AtCoder Grand Contest 016

Preface

今天居然一个早上就Rush完了所有题目(E是之前做过的),F没想出来

希望所有的AGC保佑我CSP别爆炸的说……


A - Shrinking

很显然直接枚举最后变成的是哪种字符即可,需要变化的此时时相邻两个位置的差的\(\max\)在减\(1\)

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int n,ans=1e9; char s[N];
int main()
{
	RI i,j; for (scanf("%s",s+1),n=strlen(s+1),i='a';i<='z';++i)
	{
		int cur=0,lst=0; for (j=1;j<=n;++j) if (s[j]==i) cur=max(cur,j-lst),lst=j;
		cur=max(cur,n-lst+1); ans=min(ans,cur-1);
	}
	return printf("%d",ans),0;
}

B - Colorful Hats

首先我们发现最大值与最小值的差值一定是\(\le 1\)的,否则显然无解

考虑先特判掉最大值与最小值相等的情况,对于剩下的我们发现所有为最小值的猫的帽子必定都不相同

同时对于所有为最大值的猫的帽子,颜色最少的情况是两个一组同色,根据这个判断一下即可

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],mi=1e9,mx,cmi,cmx;
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d",&a[i]),mi=min(mi,a[i]),mx=max(mx,a[i]);
	if (mx-mi>1) return puts("No"),0;
	if (mi==mx) return puts(mi==n-1||mi<=n/2?"Yes":"No"),0;
	for (i=1;i<=n;++i) if (a[i]==mi) ++cmi; else if (a[i]==mx) ++cmx;
	if (mi<=cmi-1||mi>cmi-1+cmx/2) return puts("No"),0;
	return puts("Yes"),0;
}

C - +/- Rectangle

非常Easy的构造题,看一眼就知道怎么做了

首先先给矩阵中的每个数填上一个正数\(x\),考虑从左上向右下判断每个\(h\times w\)的子矩阵,若该子矩阵内不存在权值为负数的点就在该矩阵的右下角填一个\(-(h\times w\times x+1)\)的数即可

容易发现这种构造方案一定可以保证填成负数的位置最少,最后只需要判断一下整个矩阵的和是否大于\(0\)即可

\(x\)显然不能取得过小或过大,这里取\(1000\)可以通过

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int n,m,h,w,a[N][N],sum[N][N],ans;
int main()
{
	RI i,j; scanf("%d%d%d%d",&n,&m,&h,&w);
	for (i=1;i<=n;++i) for (j=1;j<=m;++j) a[i][j]=1000;
	for (i=h;i<=n;++i) for (j=w;j<=m;++j)
	{
		sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];;
		if (sum[i][j]-sum[i][j-w]-sum[i-h][j]+sum[i-h][j-w]==0)
		++sum[i][j],a[i][j]=-1000*(h*w-1)-1; 
	}
	for (i=1;i<=n;++i) for (j=1;j<=m;++j) ans+=a[i][j];
	if (ans<0) return puts("No"),0; for (puts("Yes"),i=1;i<=n;++i)
	for (j=1;j<=m;++j) printf("%d%c",a[i][j]," \n"[j==m]);
	return 0;
}

D - XOR Replace

首先给操作的进行简化,我们考虑令\(a_{n+1}=\bigoplus_{i=1}^n a_i\),那么当前的操作就变成了选择一个\(a_i,i\in[1,n]\)\(a_{n+1}\)交换

考虑此时是否无解就很好判断了,把\(b_{n+1}\)用同样的方法求出来比较下是否每个数出现的次数相同即可

接下来考虑算答案,这里我和陈指导先手玩了下没有重复数字的情况,发现此时的答案就是需要交换的位置数+置换环的个数(不包括\(n+1\)

考虑有重复数字的时候我们直接给要修改的数字间连边,观察一下就会发现答案就是需要交换的位置数+联通块的个数减一(注意特判纯粹的单独点)

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,a[N],b[N],ca[N],cb[N],rst[N<<1],fa[N],ret; bool c[N],vis[N];
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d",&a[i]),rst[++m]=a[i],a[n+1]^=a[i];
	for (i=1;i<=n;++i) scanf("%d",&b[i]),rst[++m]=b[i],b[n+1]^=b[i];
	rst[++m]=a[n+1]; rst[++m]=b[n+1];
	for (sort(rst+1,rst+m+1),m=unique(rst+1,rst+m+1)-rst-1,i=1;i<=n+1;++i)
	++ca[a[i]=lower_bound(rst+1,rst+m+1,a[i])-rst],
	++cb[b[i]=lower_bound(rst+1,rst+m+1,b[i])-rst];
	for (i=1;i<=m;++i) if (ca[i]!=cb[i]) return puts("-1"),0;
	for (i=1;i<=m;++i) fa[i]=i; for (c[a[n+1]]=i=1;i<=n+1;++i)
	{
		if (i<=n&&a[i]!=b[i]) ++ret; int x=getfa(a[i]),y=getfa(b[i]); if (x!=y) fa[y]=x,c[x]=1;
	}
	for (i=1;i<=m;++i) if (c[getfa(i)]&&!vis[getfa(i)]) ++ret,vis[getfa(i)]=1;
	return printf("%d",ret-1),0;
}

E - Poor Turkeys

陈指导去年模拟赛直接用\(O(nm)\)DP爆切了这题,后来看了下网上题解大部分都是\(O(n^3+mn^2)\)的,看来陈指导又吊打标算了

首先我们主要要发现一个重要性质:如果操作到一对火鸡\((x,y)\),而存在一只火鸡\(z\)它不能和火鸡\(x\)同时存活,那么\(y\)显然也不能和\(z\)一起存活

因为若在操作\((x,z)\)\(x\)死了在操作\((x,y)\)\(y\)就必死了;若\(z\)死了同样\(y,z\)无法同时存活

因此我们可以设计一个状态\(f_{i,j}\)表示火鸡\(i,j\)能否一同存活,转移便十分显然了

具体实现看代码,复杂度是\(O(mn)\)的(还可以用bitset优化到\(O(\frac{mn}{32})\)),网上的什么集合求交之类的东西完全看不懂的说

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=405;
int n,m,x,y,ans; bool f[N][N];
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
	{
		scanf("%d%d",&x,&y); if (f[x][x]) f[y][y]=1; if (f[y][y]) f[x][x]=1;
		for (j=1;j<=n;++j) if (f[x][j]) f[y][j]=f[j][y]=1;
		for (j=1;j<=n;++j) if (f[y][j]) f[x][j]=f[j][x]=1;
		if (f[x][x]) for (j=1;j<=n;++j) f[x][j]=f[j][x]=1;
		if (f[y][y]) for (j=1;j<=n;++j) f[y][j]=f[j][y]=1; f[x][y]=f[y][x]=1;
	}
	for (i=1;i<=n;++i) if (!f[i][i]) for (j=i+1;j<=n;++j) ans+=!f[i][j];
	return printf("%d",ans),0;
}

F - Games on DAG

显然要考虑\(SG\)函数,DAG的\(SG\)函数就是\(\operatorname{mex}\),先手必胜的条件就是前两个点的\(SG\)函数的异或值不为零

考虑求其反面,求异或值为\(0\),即\(SG_1=SG_2\)的方案数

这里由于边数是\(O(n^2)\)级别的,因此我们不能状压边集,那就考虑状压点集

\(f_{k}\)表示考虑点集\(k\)的导出子图时\(SG_1=SG_2\)的方案数

转移的话显然时考虑枚举两个集合\(S,T\)满足\(S\cup T=k\),我们钦定\(S\)必败点集合,那么其补集\(T\)显然就是必胜点集合

考虑\(S,T\)之间连边的情况,由于必胜态存在至少一个后继状态必败态,因此每个\(T\)集合中的点要与\(S\)集合中的点连一条边,而\(S\)集合内部显然不能连边,而\(S\)\(T\)的连边任意

接下来就要考虑\(T\)内部连边的方案数了,虽然\(T\)是必胜点集合,但是单独拿出来可能还是存在\(SG\)函数为\(0\)的点的

我们假设存在一个虚拟点,使得\(T\)集合中所有点都连向这个虚拟点,这样所有点的\(SG\)值都增加了\(1\),而必败点的\(SG\)函数为\(0\),因此\(T\)内部连边并满足\(SG_1=SG_2\)的方案数就是\(f_T\)

转移的时候枚举子集即可,复杂度\(O(3^n\times n^2)\),注意一个状态必须要让\(1,2\)都存在

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=15,mod=1e9+7;
int n,m,x,y,lim,f[1<<N],g[N],bit[1<<N],ans;
int main()
{
	RI i,j,S,T; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
	scanf("%d%d",&x,&y),--x,--y,g[x]|=1<<y;
	for (lim=1<<n,i=0;i<lim;++i) bit[i]=bit[i>>1]+(i&1);
	for (f[0]=i=1;i<lim;++i) if ((i&1)==((i>>1)&1))
	{
		for (S=i;S;S=(S-1)&i) if ((S&1)==((S>>1)&1))
		{
			T=S^i; int ret=1; for (j=0;j<n;++j) if ((i>>j)&1)
			{
				if ((S>>j)&1) ret=1LL*ret*(1<<bit[g[j]&T])%mod;
				else ret=1LL*ret*((1<<bit[g[j]&S])-1)%mod;
			}
			(f[i]+=1LL*f[T]*ret%mod)%=mod;
		}
	}
	for (ans=i=1;i<=m;++i) (ans+=ans)%=mod;
	return printf("%d",(ans-f[lim-1]+mod)%mod),0;
}

Postscript

计划还算是完成了吧,明天出发去杭州了,希望能发挥出正常水平吧(别像去年一样犯病了)

posted @ 2020-11-05 20:48  空気力学の詩  阅读(99)  评论(0编辑  收藏  举报