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\n",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\times (hw-1)+1)\),剩下所有位置都放\(k\),那么每一个\(h\times 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\n",res+fl-1);
	return 0;
}

\(E\)

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

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

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

  • 如果\(u\in S\)\(v\in S\),则\(f_{S,r}=0\)

  • 如果\(u\in S\)\(v\notin S\),则\(f_{S,r}=f_{S\cup\{v\},r-1}\)

  • 如果\(v\in S\)\(u\notin S\),同上

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

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

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

前一个条件显然,对于后面那个条件,假设存在一个点\(w\),满足\(w\in S_u\)\(w\in 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\n",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\n",dec(bin[m],f[lim-1]));
	return 0;
}
posted @ 2019-09-25 18:32  源曲明  阅读(242)  评论(0编辑  收藏  举报