Codeforces Global Round 14(A-G)

感谢wxy_大佬给的E的思路

A

考场:n很小,随机化1e3次就好了。

正解:直接从1-n开始加,假如sum=x就交换 \(a[i] \ and \ a[i+1]\)

无解的情况就是 \(\sum_{i=1}^{n}a[i]=x\)

#include <bits/stdc++.h>
#define N (int)(1e3+5)

using namespace std;

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

int rd() {
	int f=1,sum=0; char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}

int main() {
	t=rd(); 
	while(t--) {
		n=rd(); m=rd();
		for(int i=1;i<=n;i++) a[i]=rd();
		int fl=0;
		for(int i=1;i<=999;i++) {
			random_shuffle(a+1,a+1+n);
			int res=0,ok=1;
			for(int j=1;j<=n;j++) {
				res+=a[j];
				if(res==m) ok=0;
			}
			if(ok) {
				puts("YES");
				for(int j=1;j<=n;j++) printf("%d ",a[j]);
				puts(""); fl=1;
				break;
			}
		}
		if(!fl) puts("NO");
	}
	return 0;
}

B

手摸下可以发现一个单位正方形只能由2个/4个正方形组成,考虑除以2 or 4之后是否是完全平方数

#include <bits/stdc++.h>
using namespace std;
int t,n;
int xx(int x) {
	return x*x;
}
int main() {
	cin>>t;
	while(t--) {
		cin>>n;
		if(n&1) puts("NO");
		else {
			bool fl=0;
			for(int i=2;i<=n;i*=2) {
				if(n%i==0&&xx(sqrt(n/i))==n/i) fl=1;
			}
			if(fl) puts("YES");
			else puts("NO");
		}
	}
	return 0;
}

C

考场:考虑2个的时候4个数 a b c d 升序

若这样选 ac bd

假设最优

a+c-b-d<a+d-b-c

得 a+b+2c<a+b+2d 显然成立

所以sort一遍 每m个一组 最后判断是否不超过x

别人的:优先队列找当前最小即可

#include <bits/stdc++.h>

#define N (int)(1e5+5) 

using namespace std;

struct node {
	int x,id;
}a[N];
int b[N],be[N],n,m,k,t;

bool cmp(node x,node y) {
	return x.x<y.x;
}

int rd() {
	int f=1,sum=0; char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}

int main() {
	t=rd();
	while(t--) {
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(be,0,sizeof(be));
		n=rd(); m=rd(); k=rd();
		for(int i=1;i<=n;i++) a[i].x=rd(),a[i].id=i;
		sort(a+1,a+1+n,cmp);
		int las=0,tot=0;
		for(int i=1;i<=n;i++) {
			int id=(i-1)/m+1;
			if(las!=id) las=id,tot=0;
			b[++tot]+=a[i].x; be[a[i].id]=tot;
		}
		int mx=0,mi=0x7fffffff;
		for(int i=1;i<=m;i++) mx=max(mx,b[i]),mi=min(mi,b[i]);
		//cout<<mx<<" "<<mi<<endl;
		if(mx-mi>k) {
			puts("NO"); continue;
		}
		puts("YES");
		for(int i=1;i<=n;i++) printf("%d ",be[i]);
		puts("");
	}
}

D

考虑l=r的情况,我们可以发现,ans=[1,l]在[l+1,r]没出现的个数。

考虑类比到l<r,最优策略应该是先统计有出现的,再将[l+1,n]中出现次数大于1的给到[1,l]总共给cnt/2个。

l>r同理

#include <bits/stdc++.h>

#define N (int)(2e5+5)

using namespace std;

bool vis[N];
int c[N],a[N],n,l,r,t;

int rd() {
	int f=1,sum=0; char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}

int main() {
	t=rd();
	while(t--) {
		memset(a,0,sizeof(a));
		memset(c,0,sizeof(c));
		n=rd(); l=rd(); r=rd();
		for(int i=1;i<=n;i++) a[i]=rd(); 
		if(l==r) {
			int res=0;
			for(int i=1;i<=l;i++) ++c[a[i]];
			for(int i=l+1;i<=n;i++) if(!c[a[i]]) ++res; else --c[a[i]];
			printf("%d\n",res);
		} else if(l<r) {
	//		sort(a+1,a+l+1); sort(a+l+2,a+n+1);
			int le=(r-l)/2,res=0,mx=0;
			for(int i=l+1;i<=n;i++) ++c[a[i]],mx=max(mx,a[i]);
			//for(int i=l+1;i<=n;i++) vis[a[i]]=1;
			for(int i=1;i<=l;i++) if(!c[a[i]]) ++res; else --c[a[i]];
			for(int i=1;i<=mx;i++) {
				if(le<=0) break; 
				if(c[i]>1) {
					if(c[i]/2>le) res+=le;
					else res+=c[i]/2;
					le-=c[i]/2;
				}
			} 
			if(le>0) res+=le*2;
			printf("%d\n",res);
		} else {
			int le=(l-r)/2,res=0,mx=0; 
			for(int i=1;i<=l;i++) ++c[a[i]],mx=max(mx,a[i]);
			for(int i=l+1;i<=n;i++) if(!c[a[i]]) ++res; else --c[a[i]];
			for(int i=1;i<=mx;i++) {
				if(le<=0) break; 
				if(c[i]>1) {
					if(c[i]/2>le) res+=le;
					else res+=c[i]/2;
					le-=c[i]/2;
				}
			} 
			if(le>0) res+=le*2;
			printf("%d\n",res);
		}
	}
}

E

考虑令开机的为1不开机的为0,那么一定不存在连续超过2个0,且每段1之间必定只有1个0间隔。

由于n只有400,考虑dp。

设f[i][j]为到i时分j段1的方案数。

初始化即 f[1][1]=1 f[i][1]=f[i-1][1]*2。

即设前面那段为a,后面单独的为b,有2种方案: ab/ba。

先枚举i,之后枚举j表示当前分几段,后面枚举k,表示第j+1段的len。

\[f[i+k+1][j+1]=(f[i+k+1][j+1]+f[i][j]*C[i+k+1-j][k]%mod*p[k-1]%mod)%mod; \]

首先 i+k+1代表着当前的长度(原来为i,这一段为k,间隔的0为1)

$ f[i][j]C[i+k+1-j][k]p[k-1]$ 代表 到i分j段的方案数乘上当前所有的1(因为j+1段,所以j个间隔0)选出k个来作为新段的组合方案再乘上k个1任意排列的方案数。

#include <bits/stdc++.h>

#define N 405
#define int long long

using namespace std; 

int f[N][N],C[N][N],p[N],mod,n;

signed main() {
	cin>>n>>mod;
	f[1][1]=p[0]=1;
	for(int i=2;i<=n;i++) f[i][1]=(f[i-1][1]*2)%mod;
	for(int i=1;i<=n;i++) p[i]=(p[i-1]*2)%mod;
	for(int i=0;i<N;i++) {
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=i;j++) {
			for(int k=1;i+k+1<=n;k++) {
				f[i+k+1][j+1]=(f[i+k+1][j+1]+f[i][j]*C[i+k+1-j][k]%mod*p[k-1]%mod)%mod;
			}
		}
	}
	int answ=0;
	for(int i=0;i<=n;i++) answ=(answ+f[n][i])%mod;
	cout<<answ;
	return 0;
}

F

首先, \((n-1)*x>sum\) 肯定不行,因为不会凭空产生。
反之,必定可以,因为总会有一种排列使得所有的前缀和都大于x*i。

我们考虑这是在树上的,当前节点为x,子节点为y。

我们可以发现,应该先考虑叶子节点,再从底层转移到上层,所以先dfs(y)。

考虑 \(val[x]+val[y]>=x\) ,于是我们直接顺序记录进去,并更新 \(val[x]=val[x]+val[y]-x\)。

否则,我们应该从x去修到y,倒序记录这条边,因为要修好x及其以上的节点才能推到y。

关于路径记录考虑开个双端队列即可。

#include <bits/stdc++.h>

#define N (int)(3e5+5)
#define int long long

using namespace std;

struct edge{
	int nex,to,id;
}e[N<<1];

bool vis[N];
int ans[N],ct1,ct2,head[N],cnt;
int n,m,v,a[N];

void dfs(int x,int fa) {
	if(vis[x]) return;
	vis[x]=1;
	for(int i=head[x];i;i=e[i].nex) {
		int y=e[i].to;
		if(y==fa||vis[y]) continue;
		dfs(y,x);
		if(a[x]+a[y]>=v) {
			a[x]=a[x]+a[y]-v;
			ans[ct1++]=e[i].id;
		} else {
			ans[--ct2]=e[i].id;
		}
	}
}

void add(int x,int y,int z) {
	e[++cnt]=edge{head[x],y,z};
	head[x]=cnt;
}

signed main() {
	cin>>n>>m>>v;
	int sum=0;
	for(int i=1;i<=n;i++) {
		cin>>a[i]; sum+=a[i];
	}
	if(sum<(n-1)*v) {
		puts("NO"); return 0;
	}
	int x,y;
	for(int i=1;i<=m;i++) {
		cin>>x>>y;
		add(x,y,i); add(y,x,i);
	}
//	for(int i=1;i<=n;i++) add(0,i,-1);	
	//g.push_back(1);
	ct2=n-1;
	dfs(1,0);
	puts("YES");
	for(int i=0;i<n-1;i++) cout<<ans[i]<<endl;
	return 0;
}

G

题解

#include <bits/stdc++.h>
 
#define N (int)(2e5+5)
#define int long long
 
using namespace std;
 
struct edge {
	int st,nex,to,w;
}e[N<<1];
int head[N],vis[N],cnt,low[N],dfn[N],tot,col,id[N],val[N],flag[N],dep[N],gc[N];
int n,m,t;
 
int rd() {
	int f=1,sum=0; char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)) {sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return sum*f;
}
 
void add(int x,int y,int z) {
	e[++cnt]=edge{x,head[x],y,z};
	head[x]=cnt;
}
 
int gcd(int x,int y) {
	return y==0?x:gcd(y,x%y);
}
 
stack<int>s;
void tarjan(int x) {
	dfn[x]=low[x]=++tot;
	flag[x]=1; s.push(x);
	for(int i=head[x];i;i=e[i].nex) {
		int y=e[i].to;
		if(!dfn[y]) {
			dep[y]=dep[x]+e[i].w;
			tarjan(y);
			low[x]=min(low[x],low[y]);
		} else if(flag[y]) {
			low[x]=min(low[x],dfn[y]);
			gc[x]=gcd(gc[x],dep[x]+e[i].w-dep[y]);
		}
	}
	if(low[x]==dfn[x]) {
		int qwq=0; 
		++col;
		do {
			qwq=s.top(); s.pop();
			id[qwq]=col; flag[qwq]=0;
		} while(qwq!=x);
	}
}
 
signed main() {
	n=rd(); m=rd();
	int x,y,z;
	for(int i=1;i<=m;i++) {
		x=rd(); y=rd(); z=rd();
		add(x,y,z);
	}	
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	for(int i=1;i<=n;i++) val[id[i]]=gcd(val[id[i]],gc[i]);
	t=rd();
	while(t--) {
		x=rd(); y=rd(); z=rd();
		//cout<<y<<" "<<val[id[x]]<<" "<<z<<endl;
		if(!id[x]) puts("NO");
		else if(y%gcd(val[id[x]],z)==0) puts("YES");
		else puts("NO");
	}
	return 0;
}

上一篇:redis基础命令第二波


下一篇:腾讯五十题 No.28 二叉树中的最大路径和