ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【周8道题】4.24完成

2021-04-24 10:04:37  阅读:201  来源: 互联网

标签:道题 const int res LL ++ 完成 -- 4.24


额,这八道题算法有些重复,其中单调类和DP似乎比较多,不过总体来说还是很有写的必要滴
我在做这些题时用到的算法有:单调类(单调队列、数组)、dp(区间dp、状压dp)、莫队、tarjin
由于比较忙碌,没有探究多种解法,谁有想法可以评论啊
我标了推荐指数,指数为*表示强烈不建议做,***表示一般题目,*****表示强烈建议去做。

[luogu]P2034 选择数字

推荐指数:***
算法:队列

先考虑dp。f[i][j]表示对于前i个数,结尾连续选了j个数时最大的数(1<=i<=n, 0<=j<=k)。那么f[i+1][j]=f[i][j-1]+a[j],f[i+1][0]=max{f[i][0],f[i][1],...,f[i][k]}。

显然f数组的求取是O(N*K)的,太慢了。但是转移过程是可以转化成队列的。
f[i][0]到f[i][k-1]只是往后顺一位加上同一个数成为f[i+1][1]到f[i+1][k],只有f[i+1][0]的计算略复杂一些。这就相当于维护一个长度为k+1的队列,每次从队尾删去一个数,从队头添加一个数。对于转移时加的那些数字,我们用sum存一下就好了。

我是用的multiset维护这个队列。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=100000+10;

int n, k;
int a[N];
LL b[N];
multiset<LL> S;

int main(){
	cin>>n>>k;
	for(int i=1; i<=n; ++i){
		scanf("%d", a+i);
	}
	
	LL ans=0, sum=0;
	S.insert(0);
	b[0]=0;
	for(int i=1; i<=n; ++i){
		sum+=a[i];
		//maxx存的是队列中的最大值
		multiset<LL>::iterator maxx=S.end(); maxx--;
                //b[i]是队列中添加的第i个元素,可以用来查找要删除的数字
		b[i]=-a[i]+*maxx;
		S.insert(b[i]);
		if(i-k>0){
			multiset<LL>::iterator tmp=S.find(b[i-k-1]);
			S.erase(tmp);
		}
		maxx=S.end(); maxx--;
		ans=max(ans,sum+*maxx);
	}
	cout<<ans<<endl;
}

[luogu]P4163 [SCOI2007]排列

推荐指数:****
算法:状压DP
一开始想的是搜索,结果TLE了。看了题解才发现是状压DP。

S的长度不超过10,这就说明S的排列不超过1024个。令f[t][i]表示以各种方式排状态t包含的这些数字时,膜d得i得结果有多少个(我们先不考虑选择了相同的数字带来的影响)
转移方程为:f[t][(j*10+s[k])%d]+=f[t^(1<<k)][j]

所以说,以后看到小数据就考虑下状压吧。不过,抛开数据范围,这题真有那么容易看出来是状压吗?我认为这道题能使用状压的原因有:

  1. 注重元素选择的顺序
  2. 计算满足交换律、结合律 i+j=j+i, i+(j+k)=(i+j)+k
  3. 数据范围小到可以O(2^n)
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

LL f[1<<10][1010];
char s[15]; int d;
LL fact3[15];
int num[15];

int main(){
	
	fact3[0]=1;
	for(int i=1; i<=10; ++i) fact3[i]=fact3[i-1]*i;
	
	int T; cin>>T;
	while(T--){
		cin>>s>>d;
		int len=strlen(s);
		for(int i=0; i<10; ++i) num[i]=0;
		for(int i=0; i<len; ++i){
			s[i]-='0';
			num[s[i]]++; 
		}
		f[0][0]=1;
		
		int t=1<<len;
		for(int p=1; p<t; ++p){
			for(int i=0; i<len; ++i){
				if(p&(1<<i)){
					for(int j=0; j<d; ++j){
						f[p][(j*10+s[i])%d]+=f[p^(1<<i)][j];
					}
				}
			}
		}
		
		LL tmp=1;
		for(int i=0; i<10; ++i){
			tmp*=fact3[num[i]];
		}
		cout<<f[t-1][0]/tmp<<endl;
		for(int p=0; p<t; ++p){
			for(int i=0; i<d; ++i) f[p][i]=0;
		}
	}
	
}

[luogu] [国家集训队]数颜色 / 维护队列

推荐指数:*****
算法:莫队

我之前不会莫队,因此通过这道题学习了莫队。
当然莫队的教程哪里都找得到,因此我就说下我对这道题的理解。
知道了[L,R]内的颜色数,可以O(1)推出[L+1,R][L-1,R][L,R+1][L,R-1]的颜色数,那么我们就可以利用这点,用一个区间的答案推出另一个区间的答案。这样一来,时间消耗的主要地方就是区间之间的转移。于是我们用分块来将转移的总消耗控制在一定范围内。

不知道你们有没有,反正我在思考这道题的时候,有一瞬间是萌发了这种想法,但另一瞬间就被否定了,也许是自己觉得不可能实现吧,毕竟区间之间的转移太扯了。然而,正解却就是如此。所以当我们陷入思考的瓶颈时,不妨大胆一些,设法想一想那些“异想天开”的思路吧。

不过还有一点是,这是带修改的莫队,blocksize的大小计算得很玄学。这块我还不是很了解,因此以后可能会写一篇学习篇之类的?

#include<bits/stdc++.h>
using namespace std;

const int V=2000000+10, N=2000000+10;//N=133333+10;
int c, n, m, tota, totb;
int blocksize;
int a[N], p[V], ans[N];

struct change{
	int pre, place, suf;
}cg[N];

struct query{
	int l, r, pre, id, bll, blr;
}q[N];

bool cmp(query a, query b){
	return a.bll!=b.bll?a.bll<b.bll:(a.blr!=b.blr?a.blr<b.blr:a.pre<b.pre);
}

int main(){
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d", a+i); a[i]%=V;
	}
	char sss[5];
	for(int i=1, l, r; i<=m; ++i){
		scanf("%s%d%d", &sss, &l, &r);
		if(sss[0]=='R'){
			r%=V;
			cg[++tota].place=l; cg[tota].suf=r;
			cg[tota].pre=a[cg[tota].place];
			a[cg[tota].place]=cg[tota].suf;
		}else{
			if(l>r) swap(l,r);
			q[++totb].l=l; q[totb].r=r; q[totb].pre=tota; q[totb].id=totb;
		}
	}
	blocksize=pow(n,0.666);
//	if(blocksize<1) return 0;
	for(int i=1; i<=totb; ++i){
		q[i].bll=(q[i].l-1)/blocksize+1;
		q[i].blr=(q[i].r-1)/blocksize+1;
	}
	for(int i=tota; i>=1; --i){
		a[cg[i].place]=cg[i].pre;
	}
	sort(q+1,q+totb+1,cmp);
	
	int l=1, r=1, num=1, t=0; p[a[1]]++;
	for(int i=1; i<=totb; ++i){
		int ll=q[i].l, rr=q[i].r, tt=q[i].pre;
		while(ll<l) num+=!p[a[--l]]++;
		while(rr>r) num+=!p[a[++r]]++;
		while(ll>l) num-=!--p[a[l++]];
		while(rr<r) num-=!--p[a[r--]];
		while(tt<t){
			int pla=cg[t].place;
			if(l<=pla && pla<=r) num-=!--p[a[pla]];
			a[pla]=cg[t--].pre;
			if(l<=pla && pla<=r) num+=!p[a[pla]]++;
		}
		while(tt>t){
			int pla=cg[++t].place;
			if(l<=pla && pla<=r) num-=!--p[a[pla]];
			a[pla]=cg[t].suf;
			if(l<=pla && pla<=r) num+=!p[a[pla]]++;
		}
		if(q[i].id<N) ans[q[i].id]=num;
	}
	for(int i=1; i<=totb; ++i) printf("%d\n", ans[i]);
}

[SCOI2006]整数划分

推荐指数:***
算法:高精度,数论

我数论不好,这道题有些做不来......

定理:n只能分成2、3、4这三种数。即若n>4,则n分成n-3和3,这样不断分下去,直到n<=4。
证明:若n分成的数中有一个是k(k>4),那么(k-3)*3-k=2k-9>=2*5-9>0即(k-3)*3>k,也就是把k分成k-3和3更好。

剩下的只要高精度算就好了,也不需要整什么傅里叶变换(我当初就是看到这个标签才选的这道题,害)

#include<bits/stdc++.h>
using namespace std;

const int N=31000;

struct Number{
	int a[5010], sz, M;
	Number(){
		M=10000;
		sz=1;
		a[1]=1;
	}
	
	void Mul(int t){
		int jin=0;
		for(int i=1; i<=sz; ++i){
			a[i]=jin+a[i]*t;
			jin=a[i]/M;
			a[i]%=M;
		}
		if(jin){
			a[++sz]=jin;
		}
	}
}res;

int main(){
	int n; cin>>n;
	while(n>4){
		res.Mul(3);
		n-=3;
	}
	if(n==4) res.Mul(4);
	else if(n==3) res.Mul(3);
	else res.Mul(2);
	
	int tmp=res.a[res.sz];
	int cnt=0;
	while(tmp){
		cnt++;
		tmp/=10;
	}
	cout<<res.sz*4-4+cnt<<endl;
	cout<<res.a[res.sz]; int sum=cnt; 
	for(int i=res.sz-1; sum<100&&i>0; --i){
		if(sum<96){
			//cout<<res.a[i];
			printf("%04d", res.a[i]);
			sum+=4;
		}else{
			int tmp=10000, resa=res.a[i];
			for(; sum<100; ++sum){
				tmp/=10;
				cout<<resa/tmp;
				resa%=tmp;
			}
		}
	}
}

[luoguP3916]图的遍历

推荐指数:**** (可以学下tarjin)
算法:tarjin、反向建图

方法一:tarjin
我一开始就想的这个方法,然后就没再深入地思考了。如果这是一棵树的话,直接dfs就完事了,所以缩点就行了。

方法二:反向建边
直接反向建边,dfs就完事了,按序号从高到低枚举每个点,把它能到达的点标记上就行了,更加简单。

tarjin:

#include<bits/stdc++.h>
using namespace std;

const int N=100000+10;

struct Graph{
	struct Edge{
		int to, next;
		Edge(int to=0, int next=0):to(to),next(next){}
	}e[N];
	int en, h[N];
	Graph(){
		en=0;
		memset(h,0,sizeof(h));
	}
	
	void add(int u, int v){
		e[++en]=Edge(v,h[u]);
		h[u]=en;
	}
}g1, g2;

int ord=1, top=0;
int dfn[N], low[N], belong[N], sta[N], rep[N];
bool vis[N];
int num;
void Tarjin(int u){
	dfn[u]=low[u]=++ord;
	vis[u]=true;
	sta[++top]=u;
	int TOP=top;
	for(int p=g1.h[u]; p; p=g1.e[p].next){
		int v=g1.e[p].to;
		if(!dfn[v]){
			Tarjin(v);
			low[u]=min(low[u],low[v]);
		}else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		num++;
		for(int i=TOP; i<=top; ++i){
			vis[sta[i]]=false;
			belong[sta[i]]=num;
			rep[num]=max(rep[num],sta[i]);
		}
		top=TOP-1;
	}
}

int n, m;

int res[N];

void dfs(int u){
	vis[u]=true;
	res[u]=rep[u];
	for(int p=g2.h[u]; p; p=g2.e[p].next){
		int v=g2.e[p].to;
		if(!vis[v]) dfs(v);
		res[u]=max(res[u],res[v]);
	}
}

int main(){
	cin>>n>>m;
	for(int i=1, u, v; i<=m; ++i){
		scanf("%d%d", &u, &v);
		g1.add(u,v);
	}
	
	for(int i=1; i<=n; ++i){
		if(!dfn[i]) Tarjin(i);
	}

	for(int i=1; i<=n; ++i){
		int u=belong[i];
		for(int p=g1.h[i]; p; p=g1.e[p].next){
			int v=g1.e[p].to; v=belong[v];
			if(u!=v) g2.add(u,v);
		}
	}
	
	for(int i=1; i<=n; ++i){
		if(!vis[belong[i]]) dfs(belong[i]);
	}
	
	for(int i=1; i<=n; ++i){
		printf("%d ", res[belong[i]]);
	}
	return 0;
}

反向建边:

#include<bits/stdc++.h>
using namespace std;

const int N=100000+10;
vector<int> V[N];

int n, m;
int a[N];
int res[N];

void dfs(int u, int cur){
	res[u]=cur;
	for(int i=V[u].size()-1; ~i; --i){
		int v=V[u][i];
		if(res[v]) continue;
		dfs(v,cur);
	}
}

int main(){
	int n, m;
	cin>>n>>m;
	for(int i=1, u, v; i<=m; ++i){
		scanf("%d%d", &u, &v);
		V[v].push_back(u);
	}
	
	for(int i=n; i>=1; --i){
		if(!res[i]) dfs(i,i);
	}
	for(int i=1; i<=n; ++i){
		printf("%d ", res[i]);
	}
}

[cf793D] Presents in Bankopolis

推荐指数:***
算法:区间dp

题意:一条街道上从左往右有n个路口。有m个自行车道的单向道路,他们都有一个起点u和终点v(不能从u、v之间离开道路)和一个困难程度d。现在你要在k个路口放置礼物,如果你在某个路口放置了礼物,那么你之后就不能再经过这个路口。经过一个路口的定义是,这个路口在你走的自行车道的起点和终点之间。现在你能从任意一个路口出发,请问你最少要经历多少困难程度才能完成目标?(最终的困难程度是你走过的路的困难程度之和,且你只能走k-1条道路)如果不能实现,输出-1。
1<=n,k<=80 0<=m<=2000 1<=d_i<=1000

思路:根据不能经过同一个路口的条件,如果我们当前的道路是l->r,那么下次只能选r->k或是t<-r(t>l)。这些区间之间的关系是包含或相切,因此可以用区间dp。令f[l][r][k][0或1]表示在区间[l,r]上已经选了k条路且端点为左边(0为左边,1为右边)时最少的困难程度。
转移方程为f[l][r][k][0]=min{f[t][r][k-1][1]+d[l][r],f[t][r][k-1][0]+d[l][t]},t在l和r之间。具体请看代码。

#include<bits/stdc++.h>
using namespace std;
 
const int INF=1000000007;
const int N=81;
 
int n, m, p;
int c[N][N], f[N][N][N][2];
 
int main(){
	cin>>n>>p>>m;
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j) c[i][j]=INF;
	}
	for(int i=1, x, y, dis; i<=m; ++i){
		scanf("%d%d", &x, &y);
		scanf("%d", &dis);
		c[x][y]=min(dis,c[x][y]);
	}
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j){
			for(int k=0; k<=p; ++k){
				f[i][j][k][0]=f[i][j][k][1]=INF;
			}
		}
	}
	for(int i=1; i<=n; ++i){
		f[i][i][1][0]=f[i][i][1][1]=0;
	}
	for(int d=1; d<n; ++d){
		for(int i=1; i+d<=n; ++i){
			int j=i+d;
			for(int k=2; k<=p; ++k){
				for(int i2=i+1; i2<j; ++i2){
					f[i][j][k][0]=min(f[i][j][k][0],f[i2][j][k-1][0]+c[i][i2]);
					f[i][j][k][0]=min(f[i][j][k][0],f[i2][j][k-1][1]+c[i][j]);

				}
				f[i][j][k][0]=min(f[i][j][k][0],f[j][j][k-1][0]+c[i][j]);
				for(int j2=j-1; j2>i; --j2){
					f[i][j][k][1]=min(f[i][j][k][1],f[i][j2][k-1][1]+c[j][j2]);
					f[i][j][k][1]=min(f[i][j][k][1],f[i][j2][k-1][0]+c[j][i]);
				}
				f[i][j][k][1]=min(f[i][j][k][1],f[i][i][k-1][1]+c[j][i]);
			}
		}
	}
	int ans=INF;
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j){
			ans=min(ans,f[i][j][p][0]);
			ans=min(ans,f[i][j][p][1]);
		}
	}
	cout<<(ans==INF?-1:ans)<<endl;
}

[cf359C] Prime Number

推荐指数:***
算法:数论

题目挺短的,而且公式也多,就不翻译了。

一道数论唬人题目,只要不被吓住,就没什么难度。
由于x是质数,因此gcd一定是x的幂。我们只需要看分子的指数就好了。
实在过于简单,懒得写了...

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=100000+10;
const LL M=1e9+7;

LL n, x, sum;
LL a[N], b[N], val[N], cnt[N];

LL power(LL x, LL p){
	LL res=1, tmp=x;
	while(p){
		if(p&1) res=res*tmp%M;
		tmp=tmp*tmp%M;
		p>>=1;
	}
	return res;
}

int main(){
	cin>>n>>x;
	for(int i=1; i<=n; ++i){
		scanf("%lld", a+i);
		sum+=a[i];
	}
	for(int i=1; i<=n; ++i){
		b[i]=sum-a[i];
	}
	sort(b+1,b+n+1);
	int valn=0;
	val[++valn]=b[1]; cnt[valn]=1;
	for(int i=2; i<=n; ++i){
		if(b[i]==val[valn]){
			cnt[valn]++;
		}else{
			val[++valn]=b[i];
			cnt[valn]=1;
		}
	}
	int vall=1;
	while(cnt[vall]%x==0){
		if(vall<valn && val[vall+1]==val[vall]+1){
			cnt[vall+1]+=cnt[vall]/x;
			vall++;
		}else{
			val[vall]++;
			cnt[vall]/=x;
		} 
	}
	printf("%lld\n", power(x,min(val[vall],sum)));
	return 0;
}

[cf629D] Babaei and Birthday Cake

推荐指数:***
算法:单调数组(我想大概就叫这个名字)

题意:你有n块蛋糕,编号1到n且每块都有一个体积v_i。你能把第i块放到第j块之上的条件是i>j且v_i>v_j。现在问你最大能摞成多大体积。

根据第一个限制i>j,我们按编号从小到大考虑。根据第二个限制v_i>v_j,我们可以维护一个单调数组。假设已经考虑了前i个蛋糕,我们把他们组成的最优解排序。各个最优解都有两个值last和vol,分别表示放在最上面的蛋糕的体积和总的蛋糕的体积,可以发现last越大,vol就越大。因此我们把这些最优解按last从小到大排序。那么加入第i+1块蛋糕后,就需要更新这个数组。假设t.last是最优解中最大的小于v_{i+1}的last,那么我们可以向数组中加入一个最优解,它的last=v_{i+1},vol=t.vol+v_{i+1}。然后我们还需要删去一些解,来维护“last越大,vol越大”这个性质。所有上面这些操作,可以用set实现。

总的来说还是比较简单的。

#include<bits/stdc++.h>
const double PI=acos(-1);
using namespace std;

struct Data{
	double last, vol;
	bool operator<(const Data&t)const{
		return last<t.last;
	}
};

const int N=100010;
int n;
double v[N];
set<Data> S;

int main(){
	cin>>n;
	for(int i=1, r, h; i<=n; ++i){
		scanf("%d%d", &r, &h);
		v[i]=PI*r*r*h;
	}
	Data d;
	d.last=d.vol=0;
	S.insert(d);
	d.last=PI*10000*10000*10010; d.vol=d.last*N;
	S.insert(d);
	for(int i=1; i<=n; ++i){
		d.last=v[i];
		set<Data>::iterator it=S.lower_bound(d);
		it--;
//		printf("--- %lf\n", v[i]);
		d.vol=it->vol+v[i];
		it++;
		if(it!=S.end()){
			for(set<Data>::iterator it2=it++; it2->vol<d.vol; it2=it++){
				S.erase(it2);
				if(it==S.end()) break;
			}
		}
		S.insert(d);
//		printf("+++ %lf %lf\n", d.last, d.vol);
	}
	set<Data>::iterator it=S.end();
	printf("%.10lf\n", (--(--it))->vol);
}

标签:道题,const,int,res,LL,++,完成,--,4.24
来源: https://www.cnblogs.com/white514/p/14695276.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有