AtCoder Grand Contest 010题解

传送门

\(A\)

判一下奇数的个数就行了

const int N=1e5+5;
int a[N],n,res;
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]),res^=(a[i]&1);
	puts(res?"NO":"YES");
	return 0;
}

\(B\)

首先记录一下所有数的总和\(sum\),然后判断一下加到这个\(sum\)需要进行几次操作

我们考虑能否把该序列变为\(0\),把序列给差分,即令\(b_i=a_i-a_{i-1},b_1=a_1-a_n\),那么发现一次操作之后会令所有\(b_i-=1\)以及某一个\(b_i+=n\)

我们最终的目的首先要让所有\(b_i\)变为\(0\),又因为所有\(b_i\)的和为\(0\)且一次操作之后总和不变,我们只要让所有\(b_i\)相等即可,而操作的时候只有\(+n\)这个操作会改变相对大小,那么判断一下所有数能否通过\(+n\)变为最大的数就好了

如果可以,那么记此时需要的操作次数为\(cnt\),如果满足\(sum\geq cnt\)并且\(sum-cnt\)\(n\)的倍数就说明可行了

//quming
#include<bits/stdc++.h>
#define R register
#define int long long
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=1e5+5;
int a[N],b[N],n,sum,res;
signed main(){
	scanf("%lld",&n);
	fp(i,1,n)scanf("%lld",&a[i]),sum+=a[i];
	if(sum%(n*(n+1)>>1))return puts("NO"),0;
	sum/=(n*(n+1)>>1);
	fp(i,2,n)b[i]=a[i]-a[i-1];b[1]=a[1]-a[n];
	sort(b+1,b+1+n);
	fp(i,1,n-1)if((b[i+1]-b[i])%n)return puts("NO"),0;
	fp(i,1,n-1)res+=(b[n]-b[i])/n;
	if(sum<res||(sum-res)%n)return puts("NO"),0;
	puts("YES");
	return 0;
}

\(C\)

随便选一个不是叶子的点做根,那么对于一个不是叶子的点,经过它的路径必然有一端来自它的一棵子树。对于点\(u\),假设它的子树里会分别延伸出\(f_v\)条路径,记\(sum=\sum f_v\)\(cnt\)为相互连接了的路径条数,那么\(u\)被覆盖的次数就是\(sum-cnt\),而且剩下的\(sum-cnt*2\)条路径会继续往上延伸

那么我们记\(f_u\)表示使\(u\)以及子树内都满足条件,此时\(u\)需要往上延伸多少条路径,树形\(dp\)即可,记得最后判一下\(f_1\)是否为\(0\)

具体细节看代码

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct eg{int v,nx;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v){e[++tot]={v,head[u]},head[u]=tot;}
ll f[N];int deg[N],a[N],rt,n;
void dfs(int u,int fa){
	if(deg[u]==1)return f[u]=a[u],void();
	R ll sum=0,mx=0;
	go(u)if(v!=fa)dfs(v,u),cmax(mx,f[v]),sum+=f[v];
	R ll cnt=((mx<<1)<=sum?sum>>1:sum-mx);
	if(sum<a[u]||sum-a[u]>cnt){puts("NO");exit(0);}
	f[u]=sum-((sum-a[u])<<1);
}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	for(R int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u),++deg[u],++deg[v]; 
	if(n==2)return puts(a[1]==a[2]?"YES":"NO"),0;
	fp(i,1,n)if(deg[i]!=1){rt=i;break;}
	dfs(rt,0);
	puts(f[rt]?"NO":"YES");
	return 0;
}

\(D\)

博弈日常不会做

这题的结论:如果有奇数个偶数则先手必胜

首先这里的所有数\(\gcd\)\(1\),代表着所有数里至少有一个奇数(很重要的性质,然而并没有发现)

那么先手一开始先将一个偶数变为奇数,然后就可以搬把小凳子来等着赢了:

后手如果选择将一个奇数变为偶数,先手可以选择一个偶数使其变为奇数

先手如果选择将一个偶数变为奇数,先手可以选择另一个偶数使其变为奇数

那么无论何时偶数的个数都为偶数,且因为这个过程中序列中至少存在一个奇数,所以\(\gcd\)绝对不会是偶数,也就是说奇偶的个数不会改变

也就是说先手赢定了

那么后手只能等死了么?或者如果有偶数个偶数就先手必胜?

回来考虑有偶数个偶数的情况,如果奇数的个数大于\(1\),那么先手不管怎么操作都会导致都会变成上面那种局面导致后手必赢,不过如果此时奇数的个数为\(1\),那么先手令这个奇数变为偶数,会导致“所有数中必定有一个奇数”这个条件不存在了,那么我们就递归考虑就可以了

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=1e5+5;
int a[N],n;
bool solve(){
	fp(i,1,n)if(a[i]&1){
		if(a[i]==1)return 0;
		--a[i];break;
	}
	R int d=0;
	fp(i,1,n)d=__gcd(d,a[i]);
	fp(i,1,n)a[i]/=d;
	R int t=0,sz=0;
	fp(i,1,n)t^=(a[i]&1^1),sz+=(a[i]&1);
	if(t||sz!=1)return t^1;
	return solve()^1;
}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	R int t=0,sz=0;
	fp(i,1,n)t^=(a[i]&1^1),sz+=(a[i]&1);
	if(t||sz!=1)return puts(t?"First":"Second"),0;
	puts(solve()?"First":"Second");
	return 0;
}

\(E\)

模型日常不会转化

首先我们假设\(A\)已经把序列重新排列好了,那么\(B\)要怎么做才能使得字典序最大

容易发现如果两个数不互质,那么这两个数的相对顺序是不变的,另一方面,如果新的序列和原来序列每两个不互质的数相对位置不变,证明这个新的序列合法

那么如果\(i<j\)\(\gcd(a_i,a_j)\neq 1\),我们就从\(i\)\(j\)连一条边,最后只要求这个图字典序最大的拓扑序就可以了。鉴于这个图有着如果\(a_i=a_j\)\(i,j\)有相同的出边的优秀性质,所以我们可以直接贪心的跑出最大的字典序

然后来考虑\(A\)在这种情况下该怎么卡,先把之前连的边变成无向边,然后我们需要对所有边定向。首先各个连通块之间是独立的,因为对于两个连通块形成的序列,\(B\)一定可以把这两个序列合并成字典序最大的而不改变每个序列里的相对顺序。

所以考虑单独一个联通快该怎么卡,首先我们找到这个连通块中最小的点\(u\),并以\(u\)为根节点随便跑一棵生成树,那么我们发现这样的话这个连通块中\(B\)能选择的第一个点只能是\(u\)了。那么我们继续考虑\(A\)如何让之后的也尽量优,\(A\)可以删掉点\(u\),然后从小到大枚举它的每一个出点,并对每一个出点也执行这个过程。我们发现最后会形成一棵\(dfs\)生成树,而且这个\(dfs\)生成树就是最优的,那么其它边实际上并不会影响最终的序列了

然后没有然后了

//quming
#include<bits/stdc++.h>
#define R register
#define pb push_back
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2005+5,M=5e6+5;
struct eg{int v,nx;}e[M];int head[N],tot;
inline void add(R int u,R int v){e[++tot]={v,head[u]},head[u]=tot;}
vector<int>to[N];int deg[N],vis[N],a[N],n;
priority_queue<int>q;
void dfs(int u){
	vis[u]=1;
	for(auto v:to[u])if(!vis[v])add(u,v),++deg[v],dfs(v);
}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	fp(i,1,n)fp(j,i+1,n)if(__gcd(a[i],a[j])>1)to[i].pb(j),to[j].pb(i);
	fp(i,1,n)if(!vis[i])dfs(i);
	fp(i,1,n)if(!deg[i])q.push(i);
	for(R int u;!q.empty();){
		u=q.top(),q.pop();
		printf("%d ",a[u]);
		go(u)if(!--deg[v])q.push(v);
	}
	return 0;
}

\(F\)

我为什么总是想了一点点就停止了……明明再想下去就能出来了……

首先我们枚举初始时棋子放在那个点上,然后以这个点为根\(dfs\)

对于一个点,定义\(f_u\)表示游戏只在\(u\)的这个子树中(即棋子不会出去),且初始时棋子在\(u\),那么先手必胜还是必败

那么最后\(f_{rt}\)就是根节点的答案

先给出结论:

\(1.\)若存在点\(v\)满足\(a_u>a_v\)\(v\)子树中先手必败,则\(u\)先手必胜

\(2.\)否则\(u\)先手必败(或者\(u\)是叶子)

先证明第一个,首先\(a_u>a_v\)证明\(a_u\neq 0\),那么肯定可以下去,而此时如果后手想从\(v\)回来,\(u\)可以又让它下去。因为\(a_u>a_v\),所以如果一直这么秦王绕柱走的话最后肯定先手必胜,所以\(v\)肯定不会回来了,也就是说接下来的游戏的确只在\(v\)的子树中。然而万一后手的目的是通过这样秦王绕柱走来减小\(a_v\)呢?我们发现及时\(a_v\)减小,\(v\)子树内的必败也不会变为必胜,所以并没有影响

关于第二个的证明,首先叶子不用说了,否则假设\(u\)到了\(v\),那么必有\(v\)子树必胜或者\(a_u\leq a_v\),如果是后者那么后手跟先手秦王绕柱走,先手必死。如果是前者后手按子树里的必胜策略走就是,还是必胜

那么直接\(O(n^2)\)就可以了

\(ps:\)鉴于题解里那一句挑(调)衅(戏)的话,顺便写了一下\(O(n)\)的,就是在\(O(n^2)\)的基础上加个换根就行了

这里是\(O(n^2)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=5005;
struct eg{int v,nx;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v){e[++tot]={v,head[u]},head[u]=tot;}
int f[N],a[N],n;
void dfs(int u,int fa){
	f[u]=0;
	go(u)if(v!=fa&&a[u]>a[v])dfs(v,u),f[u]|=f[v]^1;
}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	for(R int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u);
	fp(i,1,n)dfs(i,0);
	fp(i,1,n)if(f[i])printf("%d ",i);
	return 0;
}

这里是\(O(n)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=5005;
struct eg{int v,nx;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v){e[++tot]={v,head[u]},head[u]=tot;}
int ans[N],f[N],a[N],sz[N],n;
void dfs1(int u,int fa){
	f[u]=0;
	go(u)if(v!=fa){
		dfs1(v,u),++sz[u];
		if(a[u]>a[v])f[u]|=!f[v];
	}
}
void dfs2(int u,int fa,int pa){
	pa=(a[u]>a[fa]&&!pa);
	ans[u]=f[u]|pa;
	vector<int>st(sz[u]+2),Pre(sz[u]+2),suf(sz[u]+2);
	int top=0;
	go(u)if(v!=fa)st[++top]=v;
	Pre[0]=0;fp(i,1,top)Pre[i]=Pre[i-1]|(a[u]>a[st[i]]&&!f[st[i]]);
	suf[top+1]=0;fd(i,top,1)suf[i]=suf[i+1]|(a[u]>a[st[i]]&&!f[st[i]]);
	fp(i,1,top)dfs2(st[i],u,Pre[i-1]|suf[i+1]|pa);
}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	for(R int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u);
	dfs1(1,0),dfs2(1,0,1);
	fp(i,1,n)if(ans[i])printf("%d ",i);
	return 0;
}
posted @ 2019-09-04 16:45  源曲明  阅读(190)  评论(0编辑  收藏  举报