Codeforces Round 897 (Div. 2)补题

green_gold_dog, array and permutation(Problem - A - Codeforces

题目大意:现有一个数组a[]和一个排列b[],要求使输出一个使ci=ai-bi中ci不同值最多的排列b[].

思路:因为这里不涉及绝对值,所以我们直接将a从小到大排序然后b从大到小赋值即可。为了保证顺序,记录一下a每个值的下标,赋值的时候,将值按照下标写入新数组。

#include<bits/stdc++.h>
using namespace std;
int a[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,x;
		scanf("%d",&n);
		vector<pair<int,int>>p;
		for(int i=1;i<=n;i++) 
		{
			scanf("%d",&x);
			p.push_back({x,i});
		}
		sort(p.begin(),p.end());
		for(int i=0;i<n;i++)
		{
			auto it=p[i].second;
			a[it]=n-i;
		}
		for(int i=1;i<=n;i++) printf("%d ",a[i]);
		printf("\n");
	}
}

XOR Palindromes(Problem - B - Codeforces

题目大意:有一个01串s,我们定义一个新的01串s1,要求两者对应位取异或后得到的是一个回文串,现在我们只需要知道s1中1的个数,并定义一个新串t,如果的s1中1的个数可以为i,那么t[i]=1,否则t[i]等于0,输出t.

思路:首先找出将s变成回文串的最小操作,0^1=1,0^0=0,1^1=0,所以,s中对应位置不相同的,其中一个位置对应的s1中就是1,那么1的个数就多1.然后如果变成回文串后,那么奇数长度则s1中可以再多加任意多个1,偶数长度则每次加两个1即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		string s;
		cin>>s;
		int t1=0,t0=0,y=0;
		for(int i=0,j=n-1;i<j;i++,j--)
		{
			if(s[i]==s[j]&&s[j]=='0') t0++;
			else if(s[i]==s[j]&&s[j]=='1') t1++;
			else y++;
		}
		if(y==0)
		{
			if(n%2)
			{
				for(int i=1;i<=n+1;i++) printf("1");
			}
			else
			{
				for(int i=0;i<=n;i++)
				{
					if(i%2) printf("0");
					else printf("1");
				}
			}
		}
		else
		{
			string s1="";
			for(int i=0;i<=n;i++) s1 += '0';
			s1[y]='1';
			int r=n-2*y;
			if(r%2)
			{
				for(int i=1;i<=r;i++) s1[y+i]='1'; 
			}
			else
			{
				for(int i=2;i<=r;i+=2) s1[y+i]='1'; 
			}
			cout<<s1;
		}
		cout<<endl;
	}
}

Salyg1n and the MEX Game(Problem - C - Codeforces

题目大意:一道交互题,现有一个数组a[],A,B做游戏,A可以往里面添加任意一个不在a[]中的非负数,B可以移走任意一个存在于数组中,同时严格小于A的非负数。当B不能执行操作时,游戏结束,得分为mex(a)(不在a[]中的最小非负数),A想要最大化结果,B想要最小化结果,你代表A,oj代表B,清完成交互过程。

思路:这道题思路比较简单,我们先找出mex(a),然后输出这个值,那么oj会输入一个小于这个值的值,我们只用输出和oj输出的值一样的值即可。原理就是,先找出初始mex,如果这里不补的话,那么即使插入更大的值,结果也是这个,所以需要补一下,然后B移走更小的值,那么我们再补即可,因为B能动的数严格小于A放的数,那么不会是死循环。重点就是输入输出的处理,毕竟是交互题。

#include<bits/stdc++.h>
using namespace std;
int s[100010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,c;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&s[i]);
		int mi=0;
		for(int i=1;i<=n;i++)
		{
			if(s[i]!=mi) break;
			mi++;
		}
		printf("%d\n",mi);
		fflush(stdout);
		while(1)
		{
			scanf("%d",&c);
			if(c==-1) break;
			printf("%d\n",c);
			fflush(stdout);
		}
		if(c==-1) continue;
	}
	return 0;
}

Cyclic Operations(Problem - D - Codeforces

题目大意:给定一个长为n的数组a[],数组的初值全为0,我们每次可以在n的范围内任选k个数l1,l2,...,lk,可以对数组a[]执行操作:a[li]=l[i%k+1],先给定一个数组b[],问能否从a[]转化成b[]。

思路:这种需要进行操作的题目,我们就先分析操作的本质,也就是操作究竟干了什么,先举个例子,如果l=[1,5,6],那么就是a[1]=5,a[5]=6,a[6]=1,相当于内部转一圈,也就是一个循环,如果我们进行多个操作,而且操作与操作之间还有重叠部分会怎样呢?如果只看数太过抽象,我们可以画一个图,在i和l[i]之间建一条有向边,表示i处的数从何而来:

这个图就很清楚,一个l[]可以形成一个环,当环与环重叠的时候,需要删一下边,最后得到的就是一个有环有链的图。我们如果从1开始遍历,那么到第二次到5的时候就会产生死循环,而且我们不管是从哪里开始遍历,都会以环作为结尾。而且环的大小一定是k,因为最后被更新的k个数之间才能构成完整的环。所以我们可以假设给定的数组b[]就是一个合法的数组,然后通过遍历的方式来找环。思路显然可以,但是我们遇到环了肯定要判断退出,那么就要标记一下,比如我们从1开始遍历,那么遍历结束会是在第二次访问到5的时候,然后,我们再从2开始遍历,如果将之前打的标记全部清干净,3到1之后,再继续,然后又是到5结束,那么此时记录的长度就是6,而非是3,判断不是特别方便,而且清标记的过程时间复杂度是O(n),嵌套在遍历的O(n)循环中,那么就是O(n^2)是会超时的。所以我们标记不能清,那么遍历的1就会截至,此时的长度虽然是3,但是不成环,如果以长度作为判断标准,那么前面的那个循环长度为4,就不合适。所以我们提出一个新的思路:
将每一次的遍历打上不同的标记。如同上面用不同颜色标识一样,每次遍历从1开始计数,同时将每个数第一次被遍历到时的计数记录下来,比如5第一次被遍历到时的计数是2,第二次被遍历到时计数是5,此时已经退出,然后用计数减去退出位置的第一次计数,这里的值就是环的大小,判断这个环的大小是否为k。不清标记的话,那么从2开始遍历到1结束,这里就不用判环,只用看1是否是在这层中被打上标记的,如果不是,那么就不用考虑它(对应环重叠部分不会影响未重叠部分的计数),直接开始下一次遍历。这样遍历之后,但凡有一个大小不为k的环,就说明不是通过这种操作得到的,那么就判否。

#include<bits/stdc++.h>
using namespace std;
int a[200010],ne[200010],num[200010],st[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++) 
		{
			scanf("%d",&a[i]);
			ne[i]=a[i];
			st[i]=0;
		}
		if(k==1) 
		{
			int flag=1;
			for(int i=1;i<=n;i++) 
			{
				if(ne[i]!=i)
				{
					flag=0;
					break;
				}
			}
			if(flag) printf("YES\n");
			else printf("NO\n");
			continue;
		}
		int tag=1;//标识第几次遍历
		int flag=1;
		for(int i=1;i<=n;i++)
		{
			
			if(!st[i])
			{
				int idx=i;
				int c=1;
				while(!st[idx])
				{
					st[idx]=tag;
					num[idx]=c++;
					idx=ne[idx];
				}
				if(st[idx]!=tag)
				{
					tag++;
					continue;
				}
				if(c-num[idx]!=k)
				{
					flag=0;
					break;
				}
				tag++;
			}
		}
		if(flag) printf("YES\n");
		else printf("NO\n");
	}
}

ps:这道题有两个延伸,一个这个思路最裸的用法,用来判断图中是否有环,以及图中各个环的大小,其实各个环可能有点用不了,因为我们这里其实将1-3之间的边断掉了,如果不断掉,实际上判不了,当然由于判环的思路比较简单,一个点在遍历的过程中被遍历两次就算有环,所以倒也不用这么麻烦,这里权作类比。另一个延伸就是用来处理区间覆盖问题,下次遇到区间覆盖的问题,可以从图的角度来考虑一下。

Salyg1n and Array (simple version)(Problem - E1 - Codeforces

题目大意:这是一道交互题,现有一长度为n的数组a[],给定k,我们每次可以进行询问,询问[i,i+k-1]范围的异或和,每次询问后,这段区间就会被反转一次,最多进行100次询问,要求输出数组的异或和。(另外n和k都是正偶数)。

思路:首先对于n%k==0的情况就很简单,从1开始询问,每次跳跃k,将每次的询问值异或即可。比较麻烦的就是不能整除的情况,即r=n%k,后r个元素。

如图所示,我们可以重复利用已查询区间的最后一小段,进而往后将剩余部分查出来,只要保证查询偶数次,那么重复计算异或和的部分就会被抵消掉。上图示例是只往后拓展一个,我们实际可以将最后一部分分成两块,查询两次得到,那么问题就解决了。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		if(n%k==0)
		{
			int i,ans=0;
			for(i=1;i+k-1<=n;i+=k)
			{
				cout<<"? "<<i<<endl;
				fflush(stdout);
				int c;
				cin>>c;
				ans ^= c;
			}
			cout<<"! "<<ans<<endl;
			fflush(stdout);
		}
		else
		{
		int i,ans=0;
		for(i=1;i+k-1<=n;i+=k)
		{
			cout<<"? "<<i<<endl;
			fflush(stdout);
			int c;
			cin>>c;
			ans ^= c;
		}
		int r=n-i+1;
		int ck=r/2;
		i=i+ck-1-k+1;
		for(;i+k-1<=n;i+=ck)
		{
			cout<<"? "<<i<<endl;
			fflush(stdout);
			int c;
			cin>>c;
			ans ^= c;
		}
		cout<<"! "<<ans<<endl;
		}
	}
}

Salyg1n and Array (hard version)(Problem - E2 - Codeforces

题目和上面差不多,不过变成最多只能查询57次而已,但是上面讨论的时候,已经将情况最优化了,所以这里可以直接用上面的代码。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,k;
		scanf("%d%d",&n,&k);
		if(n%k==0)
		{
			int i,ans=0;
			for(i=1;i+k-1<=n;i+=k)
			{
				cout<<"? "<<i<<endl;
				fflush(stdout);
				int c;
				cin>>c;
				ans ^= c;
			}
			cout<<"! "<<ans<<endl;
			fflush(stdout);
		}
		else
		{
		int i,ans=0;
		for(i=1;i+k-1<=n;i+=k)
		{
			cout<<"? "<<i<<endl;
			fflush(stdout);
			int c;
			cin>>c;
			ans ^= c;
		}
		int r=n-i+1;
		int ck=r/2;
		i=i+ck-1-k+1;
		for(;i+k-1<=n;i+=ck)
		{
			cout<<"? "<<i<<endl;
			fflush(stdout);
			int c;
			cin>>c;
			ans ^= c;
		}
		cout<<"! "<<ans<<endl;
		}
	}
}

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值