AtCoder Grand Contest 016题解

传送门

(A)

直接枚举最终的字母然后模拟就行了……

就这数据范围还是别学我写的这种做法了……

const int N=105;
char s[N];int las[26],mx[26],n,res;
int main(){
	scanf("%s",s+1),n=strlen(s+1),res=n;
	fp(i,1,n)s[i]-='a';
	fp(i,1,n)cmax(mx[s[i]],i-las[s[i]]-1),las[s[i]]=i;
	fp(i,0,25)cmax(mx[i],n-las[i]),cmin(res,mx[i]);
	printf("%d
",res);
	return 0;
}

(B)

所有颜色可以被分成只出现(1)次的和出现多次的,而前者的(a_i)必定比后者小(1)

如果所有的(a_i)都相等,考虑到底是全出现(1)次还是全出现多次,否则必定是形如若干个(k-1)(k),记有(t)(k-1),那么判断一下(t)个只出现一次的和(n-t)个出现多次的是否合法就行了

const int N=1e5+5;
int a[N],n;
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	if(a[1]<a[n]-1)return puts("No"),0;
	if(a[1]==a[n])return puts(a[1]==n-1||n>=(a[1]<<1)?"Yes":"No"),0;
	R int t=1;for(;a[t]==a[t+1];++t);
	puts(a[n]>t&&n>=t+((a[n]-t)<<1)?"Yes":"No");
	return 0;
}

(C)

最优策略肯定是尽量让所有子矩阵的和为(-1)

考虑这样一种方法,在每一个(h|i,w|j)((i,j))位置放一个(sk=-(k imes (hw-1)+1)),剩下所有位置都放(k),那么每一个(h imes w)的子矩阵都会包含且仅包含一个(sk)(hw-1)(k),且总和为(-1)

同时我们还要保证整个矩阵的和为正数,设(a=n/h,b=m/w)(都是下取整),那么([1,1])([ah,bw])的这个子矩阵的和必定是(-ab),而剩下的部分因为都是填(k),所以只有让(k)尽量大才有可能让整个矩阵的和为正数,那么取能取的最大的(k)就行了

//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=505,L=1e9;
int mp[N][N],n,m,h,w,k;ll sum,sk;
int main(){
	scanf("%d%d%d%d",&n,&m,&h,&w);
	if(h==1&&w==1)return puts("No"),0;
	k=(L-1)/(h*w-1),sum=1ll*k*(n*m-(n/h*h)*(m/w*w));
	sk=1ll*k*(h*w-1)+1;
	if((n/h)*(m/w)>sum)return puts("No"),0;
	puts("Yes");
	for(R int i=h;i<=n;i+=h)for(R int j=w;j<=m;j+=w)mp[i][j]=1;
	fp(i,1,n){
		fp(j,1,m)printf("%d ",mp[i][j]?-sk:k);
		puts("");
	}
	return 0;
}

(D)

并没有想到用权值建点……

首先我们把(a_{n+1})设为所有数的异或和,那么一次操作等价于交换(a_{n+1})和某个数

对于每一个(i),如果(a_i=b_i)显然不用进行操作,否则我们把它们对应的权值连一条边,这样图里会形成若干个连通块,我们发现每条边都要有一次贡献,而且跨越连通块的时候也会有一次贡献,所以答案就是边数加上联通块个数减一

不过有可能一开始最后一个元素并没有边连出去,那么他要进入某个连通块才能继续,所以这种情况下答案要(+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=2e5+5;
int a[N],b[N],c[N],d[N],fa[N],n,tot,res;map<int,int>mp;
inline int find(R int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline int ID(R int x){return mp[x]?mp[x]:mp[x]=++tot;}
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&a[i]),a[n+1]^=a[i];
	fp(i,1,n)scanf("%d",&b[i]),b[n+1]^=b[i];
	fp(i,1,n+1)c[i]=a[i],d[i]=b[i];
	sort(c+1,c+2+n),sort(d+1,d+2+n);
	fp(i,1,n+1)if(c[i]!=d[i])return puts("-1"),0;
	fp(i,1,n+1)fa[i]=i;
	fp(i,1,n)if(a[i]!=b[i]){
		R int u=ID(a[i]),v=ID(b[i]);
		fa[find(u)]=find(v),++res;
	}
//	puts("qwq");
	if(!res)return puts("0"),0;
	fp(i,1,tot)if(find(i)==i)++res;
	bool fl=1;
	fp(i,1,n)if(b[i]==a[n+1])fl=0;
	printf("%d
",res+fl-1);
	return 0;
}

(E)

好秒的题目……一开始口胡了一个(O(nmlog n))的做法结果发现题目看错了……

我们先考虑一个更一般的问题,即定义(f_{S,r})表示在(r)步之后集合(S)中的元素是否可以全部存在,是的话记为(1)否则为(0)

假设第(r)步要吃的鸡是(u,v),那么分情况讨论

  • 如果(uin S)(vin S),则(f_{S,r}=0)

  • 如果(uin S)(v otin S),则(f_{S,r}=f_{Scup{v},r-1})

  • 如果(vin S)(u otin S),同上

  • 否则(f_{S,r}=1)

对于每一个点(u),初始时令(S={u}),然后我们倒着考虑,不断往(S)中加数,如果有一次不合法就不合法,最终可以判断(u)是否有可能不被吃

然后是结论,(u,v)可以同时存在当且仅当(u,v)都有可能不被吃且(S_ucap S_v=varnothing)

前一个条件显然,对于后面那个条件,假设存在一个点(w),满足(win S_u)(win S_v),那么如果(w)在两个集合中用来救了两只不同的鸡,显然(u,v)不可能同时活着了。如果(w)用来救了同一只鸡,那么我们删掉(w),此时必定存在另一个点(p)也同时在两个集合中,继续递归考虑就行了。鉴于最后两个集合分别是({u})({v}),所以一定会有终止的时候

复杂度为(O(nm+n^3))

//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;
bool ins[405][405],vis[405];
int fr[N],to[N],n,m,res;
int main(){
	scanf("%d%d",&n,&m);
	fp(i,1,m)scanf("%d%d",&fr[i],&to[i]);
	fp(i,1,n){
		ins[i][i]=1;
		fd(j,m,1){
			R int u=fr[j],v=to[j];
			if(ins[i][u]&&ins[i][v]){vis[i]=1;break;}
			if(ins[i][u]||ins[i][v])ins[i][u]=ins[i][v]=1;
		}
	}
	fp(i,1,n)if(!vis[i])fp(j,i+1,n)if(!vis[j]){
		R int fl=1;
		fp(k,1,n)if(ins[i][k]&&ins[j][k]){fl=0;break;}
		res+=fl;
	}
	printf("%d
",res);
	return 0;
}

(F)

首先先手必赢当且仅当(1,2)点的(SG)值异或和不为(0),因为这个东西很难算,我们考虑计算(1,2)(SG)值异或为(0)的方案数

对于(U),我们设(f[U])表示点集(U)内部使得(1,2)(SG)值相等的连边方案数,计算的话,我们枚举(S),令(T=U-S),假设(S)中所有数的(SG)值为(0)(即必败态),(T)中所有数的(SG)值不为(0)

首先(S)内部肯定不能连边(必败态不能转移到必败态),而(T)中每一个点至少得有一条到(S)中的边(必胜态至少有一个后继是必败态),(S)中的任何一个点到(T)随便连边(必败态的后继全是是必胜态),最后连边方案乘上(f[T])就是对应的贡献了

注意,(1,2)必须得同时在(S)(T)中否则不合法

为什么这样转移是对的呢?因为我们的(dp)等价于把(U)分成了若干层,其中每一层的(SG)值都是一样的,而我们转移的时候可以保证(1,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 P=1e9+7;
inline void upd(R int &x,R int y){(x+=y)>=P?x-=P:0;}
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
	R int res=1;
	for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
	return res;
}
const int N=25,M=(1<<15)+5;
int to[N],sz[M],lb[M],f[M],bin[M],n,m,lim;
int main(){
	scanf("%d%d",&n,&m),lim=(1<<n);
	for(R int i=1,u,v;i<=m;++i)scanf("%d%d",&u,&v),--u,--v,to[u]|=1<<v;
	bin[0]=1;fp(i,1,m)bin[i]=mul(bin[i-1],2);
	fp(i,1,lim-1)sz[i]=sz[i>>1]+(i&1),lb[i]=i&1?0:lb[i>>1]+1;
	f[0]=1;
	fp(u,1,lim-1)if((u&1)==(u>>1&1))
		for(R int s=u;s;s=(s-1)&u)if((s&1)==(s>>1&1)){
			R int t=u^s,res=f[t];
			for(R int i=s,j=lb[i];i;i-=i&-i,j=lb[i])
				res=mul(res,bin[sz[to[j]&t]]);
			for(R int i=t,j=lb[i];i;i-=i&-i,j=lb[i])
				res=mul(res,bin[sz[to[j]&s]]-1);
			upd(f[u],res);
		}
	printf("%d
",dec(bin[m],f[lim-1]));
	return 0;
}
原文地址:https://www.cnblogs.com/yuanquming/p/11573074.html