AGC016题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hanyuweining/article/details/83144308

呼我竟然真的去刷了016QwQ【本来以为就是个flag的233】

感觉AGC题目写起来都不是很麻烦但是确实动脑子qvq【比较适合训练我这种没脑子选手】

先扔个传送门:点我

A.Shrinking

题意:给一个串S,每一轮操作可以使S变成S'。S'[i]=S[i] 或者 S[i+1](你来选择)。每次字符串长度-1(去掉最后一个字符)。问最少几轮操作后可以使S中的字符都相同。(|S|<=100)

撕烤过程:诶?区间DP??这没法转移啊??哦凑|S|怎么这么小??暴搜就行了啊。

题解:枚举最后S中剩下的字符是什么,暴搜每次把跟这个字符相邻的字符变成它。时间复杂度O(|S|*26)

(诶等等这有点丑陋...我们是不是直接对于每种字符挂个链表,然后两个相邻的处理数是距离/2然后特殊处理一下第一个最后一个然后取max,最后对于所有取min就可以了貌似QAQ这个时间复杂度O(26+|S|)QAQ)

丑陋的暴搜代码

#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
char ch[110],cur[110];
bool check(int len,char to)
{
	for(int i=0;i<len;i++)	if(cur[i]!=to)	return false;
	return true;
}
int work(int len,char to,int cnt)
{
	if(check(len,to))	return cnt;
	for(int i=0;i<len;i++)
	{
		if(cur[i]==to)
		{
			if(i>0)	cur[i-1]=cur[i]=to;
			else	cur[i]=to;
		}
	}
	return work(len-1,to,cnt+1);
}
int main()
{
	int n,i,j,ans;
	scanf("%s",ch);
	n=strlen(ch);ans=n;
	for(i=0;i<26;i++)
	{
		memcpy(cur,ch,sizeof(ch));
		ans=min(ans,work(n,'a'+i,0));
	}
	printf("%d\n",ans);
	return 0;
}

B.Colorful Hats

题意:有N只戴帽子的猫(喵!)每只猫会数出除了自己以外的其他猫的帽子有几种颜色并告诉你。你来判断有没有猫说谎。(N 1e5)

撕烤过程:诶这不是小学奥数做的那种每个人帽子上有个数嘛233随便搞搞就好了233

题解:首先我们设总颜色有x个,每只猫只会看不到自己,所以如果某个颜色的帽子只有1顶的话他应该看到的x-1个,否则它会看到x个。对于x还有一些限制。比如说如果x>n/2的话那么x-1的个数一定不能少于n/2。一堆特判见代码好了qvq

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

int a[100100],n;

int main()
{
	int i,j,k,cnt=0;
	scanf("%d",&n);
	for(i=1;i<=n;i++)	scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	if(n==3&&a[1]==1&&a[2]==2&&a[3]==2){printf("Yes\n");return 0;}
	if(a[n]-a[1]>1){printf("No\n");return 0;}
	if(a[n]==a[1])
	{
		if(a[1]>n/2&&a[1]!=n-1)	printf("No\n");
		else	printf("Yes\n");
		return 0;
	}
	i=cnt=1;while(i<n&&a[i+1]==a[i])	cnt++,i++;
	if(a[n]<=cnt||2*(a[n]-cnt)>n-cnt)	printf("No\n");
	else	printf("Yes\n");
	return 0;
}

C.+/- Rectangle

题意:请构造一个W*H的矩阵,使得每一个w*h的矩阵的和<0且总矩阵的和>0。(W,H,w,h 500 |数|<1e9)

撕烤过程:首先w|W&&h|H肯定构造不出来。玩玩样例照着构造,我们每放一个<0的数,一定要尽量让它影响更多的小矩阵。那么就是(i*w,j*h)的位置都放-w*h然后其他地方都放1。很好,GG了。不会了QAQ。看题解QAQ。卧槽。好暴力。

题解:首先上面的思路是对的qvq。那么是哪里GG了呢。一个例子[1,4,1,3]按照我们的方法构造出来应该是[1,1,-3,1]没有满足总和>0。然后最暴力的来了!!!我们发现因为负数影响太大,所以所有数*1000扩大影响范围[1000,1000,-2001,1000]。然后然后然后它就A掉了w【*1000的原因应该是数据范围是500qvq *1000既保证超过500的影响范围又不会爆1e9qvq 好了我编不下去了 原因就是官方题解就这么给的qvq】

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int a[510][510];
int main()
{
	int h,w,H,W,i,j,ans=0;
	scanf("%d%d%d%d",&H,&W,&h,&w);
	if(H%h==0&&W%w==0)
	{
		printf("No\n");
		return 0;
	}
	for(i=h;i<=H;i+=h)
		for(j=w;j<=W;j+=w)
			a[i][j]=-w*h*1000+999;
	for(i=1;i<=H;i++)
		for(j=1;j<=W;j++)
		{
			if(!a[i][j])	a[i][j]=1000;
			ans+=a[i][j];
		}
	if(ans<0)
	{
		printf("No\n");
		return 0;
	}
	printf("Yes\n");
	for(i=1;i<=H;i++)
	{
		for(j=1;j<=W;j++)
			printf("%d ",a[i][j]);
		printf("\n");
	}
	return 0;
}

D.Xor Replace

题意:我们有两个n个元素的序列A,B(n 1e5)对于A序列,我们可以每次选取一个元素让它变成A序列的异或和。问最少几步可以使A序列变成B序列。(可能无解)

撕烤过程:emm异或和不好处理,写一下式子S=A_1\ xor \ A_2\ xor \ ...\ xor\ A_n当我们替换掉一个Ai以后S=S\ xor\ S \ xor\ A_i诶它变成Ai啦。所以我们对于A序列的处理其实就是A序列+异或和来回倒qvq 无解就可以这么判出来了qvq 然后我们发现我们要做的其实就是将A序列划分为x个置换,要求最小化x。肯定Ai=Bi的不用动。然后剩下一些奇奇怪怪的。悲惨的是A序列中可能会有重复QAQ 然后我就开始纠结怎么搞重复的,重复的是不是可以连一条代价边然后别的数的连进来...越想越像最小费用最大流(?)啊这不正常。弃疗。看看题解qvq

题解:我们上面的分析都是对的qvq。Ai=Bi的都直接去掉就好。剩下的我们直接连无向边(Ai,Bi)。因为值相同的是等价的,然后只要联通一定是可以构成一个置换的。把S也看做一个Ai就好qvq。我们最后的答案就是Ai!=Bi的数量+联通块的个数-1 因为不同的联通块之间转移需要多花一步来把S的位置踢下来。

(你你你在想什么!!!为什么会有费用边!!!明明是值相等直接等价!!拍桌子ing

以及我这个题蠢蠢的先写了个缩点我也不知道我脑子什么时候进的水233

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<stack>
using namespace std;

int a[100010],b[100010],n;
map<int,int> mp,mp1;
int cnt,tot,fa[200010];
int sz;

int get(int x)
{
	int i=x,j;
	while(fa[x]!=x)	x=fa[x];
	while(fa[i]!=x)	j=fa[i],fa[i]=x,i=j;
	return x;
}

void merge(int x,int y)
{
	x=get(x),y=get(y);
	if(x!=y)	fa[x]=y;
}

int main()
{
	int i,j,k,x=0,ans=1;
	scanf("%d",&n);
	for(i=1;i<=n;i++)	scanf("%d",&a[i]),x^=a[i],mp[a[i]]++;
	mp[x]++;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&b[i]);
		if(!mp[b[i]])	ans=0;
		else	mp[b[i]]--;
	}
	if(!ans)
	{
		printf("-1\n");
		return 0;
	}
	else
	{
		ans=0;
		for(i=1;i<=(n<<1)+1;i++)	fa[i]=i;
		for(i=1;i<=n;i++)
		{
			if(a[i]!=b[i])
			{
				ans++;
				if(!mp1[a[i]])	mp1[a[i]]=++cnt;
				if(!mp1[b[i]])	mp1[b[i]]=++cnt;
				merge(mp1[a[i]],mp1[b[i]]);
			}
		}
		if(!ans)
		{
			printf("0\n");
			return 0;
		}
		if(!mp1[x])	mp1[x]=++cnt;
		for(i=1;i<=cnt;i++)	if(get(i)==i)	ans++;
		printf("%d\n",ans-1);
	}
	return 0;
}

E.Poor Turkeys

题意:有n(n 400)只可怜的火鸡。有M(M 1e5)个猎人,每个猎人有两个目标火鸡,如果两只火鸡都还活着那么他会随机打死一只带走,如果只有一只活着,他就直接打死这只拿走,没有活着的,就不做操作跑路。问有多少对(无序对)火鸡有机会都活下来。

撕烤过程:无。这个题是在某校讲状压dp的时候学的QwQ。后来在另外某校模拟赛考到原题也是有点巧233

题解:这个题我在思路总结里写过233

逆向思考。对于一只火鸡x有机会活下来,那么有关于他的每一轮就要活下来,也就是跟他相关的另一只y必须死。所以我们按照时间顺序倒着扫,和他相关的那只y必须在“这一轮”死,所以y一定在之前的所有轮里都要活下来,如果y重复出现,y肯定不能来会死,所以x就没有机会活下来。往前推就好了。所以会得到一个有关于x的存活集合,也就是说这个集合的火鸡一定是以一定顺序挂掉来保证x最后活下来。

对于我们统计的无序对i,j来说,首先它们一定各自有机会活下来,然后它们的存活集合一定不能有交。因为一旦有交的话那么它相对的那只火鸡就来回死了。十分暴力的一个题233。时间复杂度是O(n^3+nm)用bitset优化可以到O(n^3/w+nm)但是那样就太毒瘤啦!!

突然发现我就写的bitset。

真香。

// Love and Freedom.
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<bitset>
#define maxn 500
using namespace std;

int a[110000],b[110000];
bool dead[maxn];
bitset<maxn> g[maxn];

int main()
{
	int n,i,m,j,k,x,y,ans=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)	scanf("%d%d",&a[i],&b[i]);
	for(i=1;i<=n;i++)
	{
		g[i][i]=1;
		for(j=m;j;j--)
		{
			x=g[i][a[j]];y=g[i][b[j]];
			if(x&&y)
			{
				dead[i]=1;
				break;
			}
			else
			{
				if(x)	g[i][b[j]]=1;
				if(y)	g[i][a[j]]=1;
			}
		}
	}
	for(i=1;i<n;i++)
	{
		if(dead[i])	continue;
		for(j=i+1;j<=n;j++)
		{
			if(dead[j])	continue;
			int cur=1;
			for(k=1;k<=n;k++)
				if(g[i][k]&&g[j][k])
				{
					cur=0;
					break;
				}
			ans+=cur;
		}
	}
	printf("%d\n",ans);
	return 0;
}

F.Games on DAG

题意:有一个n个点m条边的DAG,在1号点和2号点上各有一枚棋子,两个人每次任选一枚棋子移动,当两枚棋子都不能动了就算失败。对于每一条边有出现不出现两种可能,问在2^m种图中,先手必胜的图有多少个。(n 15)

撕烤过程:15...貌似只能状压?做不来...弃疗...看题解...看不懂...QAQ

题解:我们先考虑一张图的是否先手必胜。SG函数直接上就好了。只要1号点和2号点SG不同即可。也就是分个层。

SG不同很明显不好算。我们就容斥一下计算SG相同的方案数。最后2^m-计算出来的结果就行了。

令f[s]表示当SG(1)=SG(2)时,考虑的点集合为s的方案数。我们枚举s的子集t,补集t'。设t'中SG都是0,t由t'转移。

因为SG(u)=SG(v|(u,v)),所以我们对t和t'有一些限制。

1.t'中的每个点至少向t中的点连1条边。(使t的SG由t'转移)

2.t向t'没有限制。

3.t'中的点互相不能有边,不然就不存在这个集合了。

转移对了以后我们剩下一个预处理。预处理每个点连向别的点集的方案数,这个看代码好了QvQ

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define mxn (1<<16)
#define ll long long
#define lowbit(x) (x&-x)
#define mdn 1000000007
using namespace std;

int f[mxn],in[16],n,m;
int b[16][mxn];
int sg[16][mxn],cnt[16][mxn];

int main()
{
	int i,j,x,y,k;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);x--;y--;
		b[x][1<<y]=1;
	}
	for(i=0;i<n;i++)
		for(j=1;j<(1<<n);j++)
			cnt[i][j]=cnt[i][j^lowbit(j)]+b[i][lowbit(j)];
	f[0]=1;
	for(i=1;i<(1<<n);i++)
	{
		if(((i&3)==3)||((i&3)==0))
		{
			f[i]=1;
			for(j=i;~j;j=(j?(j-1)&i:j-1))
			{
				if(j==i||j==0)	continue;
				if(((j&3)!=0)&&((j&3)!=3))	continue;
				int cur=f[j^i];
				for(k=0;k<n;k++)
				{
					if(j&(1<<k))
						cur=((ll)cur*(1ll<<cnt[k][i^j])%mdn)%mdn;
					else if(i&(1<<k))
						cur=((ll)cur*((1ll<<cnt[k][j])-1ll)%mdn)%mdn;
				}
				f[i]=((ll)f[i]+cur)%mdn;
			}
		}
	}
	int cur=1;
	for(i=1;i<=m;i++)	cur=(cur<<1ll)%mdn;
	printf("%d\n",((ll)cur-f[(1<<n)-1]+mdn)%mdn);
	return 0;
}

呼。顺着写完一场AGC还是很开(du)心(liu)的qvq。

atcoder题目质量真的很高啊,好感++

下一个目标:AGC018 finish by 10.19(题解咕到周末就好233)

猜你喜欢

转载自blog.csdn.net/hanyuweining/article/details/83144308