Atcoder AGC010 题解

A - Addition

要么擦两个奇数,加一个偶数,要么擦两个偶数,加一个偶数。所以如果奇数的个数是奇数个,最后会剩一奇一偶,否则剩下一个偶数。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
int n,js;
int main()
{
	n=read();
	for(RI i=1;i<=n;++i) js+=read()&1;
	if(js&1) puts("NO");
	else puts("YES");
	return 0;
}

B - Boxes

首先我们可以知道要做 m = ∑ a n ( n + 1 ) 2 m=\frac{\sum a}{\frac{n(n+1)}{2}} m=2n(n+1)a次这样的操作才可能把石子取完(当然 m m m如果不是非负整数就肯定输出NO)。

然后对于一个盒子 i i i和它前面的盒子 i − 1 i-1 i1,它们可能会被做两种类型的操作,一种叫做 i i i取1个 i − 1 i-1 i1 n n n个,一种叫做 i i i x + 1 x+1 x+1 i − 1 i-1 i1 x x x个,则第一种操作会使 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=aiai1的值增加 n − 1 n-1 n1,第二种会使 d i d_i di减少 1 1 1

如果最后都取完,则 d i d_i di显然一样了,所以设第一种操作做了 x x x次,则第二种做了 m − x m-x mx次, ( n − 1 ) x − ( m − x ) + d i = 0 (n-1)x-(m-x)+d_i=0 (n1)x(mx)+di=0,解得 x = m − d i n x=\frac{m-d_i}{n} x=nmdi,如果解出来不是非负整数肯定输出NO。

于是我们知道了这 m m m次操作是从哪个盒子开始取第一个石子,也就知道了每个盒子里应该有多少石子,检验一遍即可(这个扫两遍就可以求了,具体可以看代码)。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=100005;
LL sum,nowadd,nows,a[N],orz[N],m;int n;
int work() {
	int js=0;
	for(RI i=0;i<n;++i) {
		int b=a[i]-a[(i-1+n)%n];
		if((m-b)%n||m-b<0) return 0;
		orz[i]=(m-b)/n,js+=orz[i];
	}
	if(js!=m) return 0;
	for(RI i=0;i<n;++i) {
		nows+=orz[i],nowadd+=nows;
		if(a[i]<nowadd) return 0;
		a[i]-=nowadd;
	}
	for(RI i=0;i<n;++i) {
		nows-=orz[i],nowadd-=1LL*n*orz[i],nowadd+=nows;
		if(a[i]!=nowadd) return 0;
	}
	return 1;
}
int main()
{
	n=read();
	for(RI i=0;i<n;++i) a[i]=read(),sum+=a[i];
	if(sum%(1LL*(n+1)*n/2)) {puts("NO");return 0;}
	m=sum/(1LL*(n+1)*n/2);
	if(work()) puts("YES");
	else puts("NO");
	return 0;
}

C - Cleaning

出题人你和石子有什么仇什么怨…

这个里面的花,我们在所有的叶子节点 i i i中放 a i a_i ai个小精灵。每一次所有小精灵都会往父亲飞,当一群小精灵聚集在 x x x这个节点时,从不同儿子节点飞上来的小精灵可以选择组成一对“好朋友”,并且以后不再往父亲走。接着小精灵们会开始捡石头,一对“好朋友”只会捡走一个石头,单身的小精灵每人捡走一个石头,石头必须被捡完,所以组成多少对朋友就是固定的。然后没有找到朋友的小精灵会继续往父亲节点飞。问是否所有小精灵都能找到朋友并且所有石头都要被捡完。

那么会前文已说,从每个节点继续往上飞的小精灵数是固定的,所以每个节点处组成朋友的小精灵对数也是固定的,重点在求出最多可以组成多少对朋友,判断能不能达成石头恰好被捡完这个目标。

那么这个找朋友的过程怎么进行呢?我们让从同一个儿子节点飞上来的小精灵暂时站在一艘船上,由于只有不同船上的小精灵可以组队,所以有精灵的船越多越有利。所以我们每次选择两艘小精灵数最多的船,让它们上面各一个小精灵组成一对,然后她们两个下船,以此类推。

于是我们发现最多能组的队数只有两种情况,一种是有一艘船上的小精灵特别多,多到别的船都空了这艘船上还有很多小精灵单身。一种是我们这么操作着操作着所有船上的小精灵数越来越多接近,最后几乎所有的小精灵都有了朋友,但是如果总精灵数是奇数,那必然有一个小精灵找不到朋友。

最多组的队数与想要石头恰好被捡完应该组的队数比较即可判断。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=100005;
int n,tot,h[N],ne[N<<1],to[N<<1],du[N];
LL a[N],f[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	if(du[x]==1) {f[x]=a[x];return;}
	LL orzabs=0,p,mx=0;
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		dfs(to[i],x),f[x]+=f[to[i]],mx=max(mx,f[to[i]]);
	}
	if(mx>f[x]-mx) p=f[x]-mx;
	else p=f[x]/2;
	if(f[x]<a[x]) {puts("NO");exit(0);}
	if(f[x]-a[x]>p) {puts("NO");exit(0);}
	f[x]-=(f[x]-a[x])*2LL;
}
int main()
{
	int x,y;
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read();
	if(n==2) {
		if(a[1]==a[2]) puts("YES");
		else puts("NO");
		return 0;
	}
	for(RI i=1;i<n;++i)
		x=read(),y=read(),add(x,y),add(y,x),++du[x],++du[y];
	for(RI i=1;i<=n;++i) if(du[i]>1) {
		dfs(i,0);
		if(f[i]) puts("NO");
		else puts("YES");
		return 0;
	}
	return 0;
}

D - Decrementing

出题人你和黑板有什么仇什么怨…

假如没有所有数除以gcd这个操作(我们称为“简化版游戏”),谁输谁赢显然只与所有数取到1要走的步数的奇偶性有关。而发现只有你取完这个1之后,所有数都变成了偶数,才有可能奇偶性情况与简化版游戏取完后不同。

假如当前局面在简化版游戏下为必胜局,因为所有数肯定互质,所以至少有一个奇数。你取一个偶数,它就变成了奇数,后手无论如何都无法将所有奇数取完扭转乾坤,所以你还是必胜。

假如当前局面在简化版下为必败局,如果大于1的奇数数量不是1个,你也没法扭转乾坤,还是必败。否则你肯定去掉那个奇数,所有数除以gcd,这样来回操作最多 log ⁡ \log log次,所以递归即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=100005;
int n,a[N];
int GCD(int x,int y) {return y?GCD(y,x%y):x;}
int work() {
	LL sum=0;int js=0,x,gcd;
	for(RI i=1;i<=n;++i) {
		sum+=a[i]-1;
		if((a[i]&1)&&a[i]!=1) ++js,x=i;
	}
	if(sum&1) return 1;
	if(js!=1) return 0;
	--a[x],gcd=a[1];
	for(RI i=2;i<=n;++i) gcd=GCD(gcd,a[i]);
	for(RI i=1;i<=n;++i) a[i]/=gcd;
	return work()^1;
}
int main()
{
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read();
	if(work()) puts("First");
	else puts("Second");
	return 0;
}

E - Rearranging

出题人你和黑板有什么仇什么怨…

考虑先手已经把序列排好了,那么不互质的两个数的相对位置肯定不变,所以对于不互质的两个数,前面那个往后面那个连一条有向边,最大字典序拓扑就是最终序列。

那么现在,我们将不互质的两个数之间连一条无向边,显然我们希望大一些的数尽量在走了小一些的数后才能够被走到,所以我们建立dfs森林,每次选择能到的点中最小的那个走到。然后对新图进行最大字典序拓扑(也就是开个堆来拓扑排序)即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=2005;
int n,tot,a[N],vis[N],h[N],ne[N<<1],to[N<<1],du[N];
vector<int> orz[N];
int gcd(int x,int y) {return y?gcd(y,x%y):x;}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,++du[y];}
void dfs(int x) {
	vis[x]=1;
	for(RI i=0;i<orz[x].size();++i)
		if(!vis[orz[x][i]]) add(x,orz[x][i]),dfs(orz[x][i]);
}
priority_queue<int> q;
int main()
{
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read();
	sort(a+1,a+1+n);
	for(RI i=1;i<=n;++i)
		for(RI j=i+1;j<=n;++j)
			if(gcd(a[i],a[j])>1) orz[i].push_back(j),orz[j].push_back(i);
	for(RI i=1;i<=n;++i) if(!vis[i]) dfs(i);
	for(RI i=1;i<=n;++i) if(!du[i]) q.push(i);
	while(!q.empty()) {
		int x=q.top();q.pop();printf("%d ",a[x]);
		for(RI i=h[x];i;i=ne[i]) {
			--du[to[i]];
			if(!du[to[i]]) q.push(to[i]);
		}
	}
	return 0;
}

F - Tree Game

出题人你跟石子什么仇什么怨…

判定方法是,如果一个子树必胜,那么必定存在一个它的儿子,满足儿子的权值小于当前点的权值,并且儿子是一个必败节点。这个很显然,因为如果这样一个儿子存在的话,你直接给他移过去,如果对面不服再移回来,你就再给他移过去,移到他服为止,当然了,他也只能服。否则的话不是必胜点,那么是一个必败点咯。
——最强的yyb

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=3005;
int h[N],ne[N<<1],to[N<<1],sg[N],a[N];
int n,tot;
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	sg[x]=0;
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		dfs(to[i],x);
		if(a[to[i]]<a[x]&&!sg[to[i]]) sg[x]=1;
	}
}
int main()
{
	int x,y;
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	for(RI i=1;i<=n;++i) {dfs(i,0);if(sg[i]) printf("%d ",i);}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值