Codeforces Round 770 (Div. 2)题解

A

题目:
给定一个串,我们拥有他的一个反串,进行k次操作,每一次把当前的串加上反串加在后面,或者加在前面,问最后我们可以得到最多几种序列?
思路:
模拟一下会发现,当一个串是回文的,最终的结果一定是1。
如果最开始的串不是回文的,经过一次操作之后也会变为回文串。(所以k==0时输出1.k>0时输出2)

B

题目:
给定一个数组,a1到an,有一个初始值是x,以及x+3,ALICE做的事情是手里拿着x,然后每一次对ai进行两步操作任选其一:第一种是当前的数加上ai,第二种是当前的数异或ai。BOB最开始手里拿的是x+3.题目保证最后只有一个人能够通过一定的操作得到y值,输出是ALICE还是BOB。
思路:
思路1:【非常的巧妙啊】
对于一个已知的数a和另外一个已知的数b,进行加法操作或者异或操作,得到的最后的结果的奇偶性是一致的。
两个偶数,结果一定是两个偶数,两个奇数,结果一定是两个偶数,一奇数一偶数,结果一定是奇数。
所以对于alice最后能够得到的数是奇偶性是一定的,又因为A B 当开始两个数的奇偶性是不一样的,所以直接根据奇偶性就可以判断出来结果了。

	int T;
	cin>>T;
	while(T--){
		int n,x,y,xx;
		cin>>n>>x>>y;
		int num=0;
		for(int i=1;i<=n;i++){
			cin>>xx; if(xx&1) num++;
		}
		if(num&1)x++;
		if((x%2)==(y%2)) cout<<"Alice"<<endl;
		else cout<<"Bob"<<endl;
	}

第一次发现这个性质,看到其他人说在CF里面奇偶性是一个很容易考的话题,尽量敏感起来。

思路2:【因为想不到上面的思路,所以就....】
对于ALICE,可以把x和所有a在二进制中的每一位上面出现过几次1记录一下,新开一个数组记录y的二进制中每一位的情况。分别用数组a,b表示上面的这两种信息。从高位往低位看。(为什么?之后再解答)
如果说数组a里现在此位的权值大于b里的,如果差值是偶数,可以直接跳过。(因为在偶数的情况下,我们可以选择异或操作来相互抵消)。如果是奇数我就必须让后面位数上面的元素来向这一位提供一个1,这个可以新开一个循环来实现,如果后面不能提供,那么就一定达不到最后的要求。
如果说数组a里现在此位的权值小于b里面的,那么一定是0<1,看数组a这一位面的能不能给这一位提供一个1就可以了。

当时是过了的,但是后来发现一个问题,当我们把每一位都分开看,分开处理的时候,其实是不严谨的,比如我们现在对于第五位所有数都进行异或,然后第四位都进行加,如果第四位和第五位的1来自于相同的一个数的时候,是不可以这样的。但是。。。可能确实考察点不在这个方向。所以也是能过的。【这个代码能够写出来,感觉就是模拟水平的问题。。。】

void solve(){
	int n,x,y;
	cin>>n>>x>>y;
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	cala(x);
	for(int i=1;i<=n;i++){
		int l;
		cin>>l;
		cala(l);
	}
	calb(y);
	for(int i=60;i>=1;i--){
		if(a[i]==b[i]) continue;
		else if(a[i]>b[i]){
			if((a[i]-b[i])%2==0) continue;
			else{
				int need=2;
				for(int j=i-1;j>=1;j--){
					if(a[j]>=need) {
						a[j]-=need;
						need=0;
						break;
					}
					else{
						need-=a[j];
						a[j]=0;
						
					}
					need*=2;
				}
				if(need==0) continue;
				else{
					cout<<"Bob"<<endl;
					return ;
				}
			}
		}
		if(a[i]<b[i]){
			int need=2;
				for(int j=i-1;j>=1;j--){
					if(a[j]>=need) {
						a[j]-=need;
						need=0;
						break;
					}
					else{
						need-=a[j];
						a[j]=0;
					}
					need*=2;
				}
				if(need==0) continue;
				else{
					cout<<"Bob"<<endl;
					return ;
				}
		}
	}
	cout<<"Alice"<<endl;
}

C

题意:
有n层架子,每一层有k个数,让我们把1~nk放到架子上面,最后满足在每一层里面选定任意区间的平均数都是一个整数。
思路:
先考虑不能放的情况,任意区间的平均数的都是整数,说明每一层相邻的两个数都必须是奇偶性一致,在n
K里面一定奇数是k的倍数,奇数有n*k/2向下取整个,因此,加上这个判断就可以得出是否可以放。
题目还要求输出放的方式:对于一个等差数列,里面任意子区间的平均数一定是一个整数。
所以我们可以 1 3 5 7 一层2 4 6 8一层这样放置进去。

void solve(){
	int n,k;
	cin>>n>>k;
	if(((n*k)/2)%k!=0) {
		cout<<"NO"<<endl;
		return ;
	}
	else{
		cout<<"YES"<<endl;
		int l=1,r=2;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=k;j++){
				if(i&1){
					cout<<l<<" \n"[j==k];
					l+=2;
				}
				else {
					cout<<r<<" \n"[j==k];
					r+=2;
				}
			}
		}
	}
}

D交互题

题目:
在一个数组里面,不存在非负数,一定存在一个0,且只有一次。每一次我们可以询问? a b c 会输出a[a],a[b],a[c]里面的最大值和最小值的差距,也就是一个极差。让我们在2*n-2次询问里面找到0的位置,最后回答的时候可以回答两个位置,只要0的位置在我们回答的两个位置里面就可以。
思路:
简单提一嘴,1800的大空白没有做出来这道题哦。
所有序列都是非负的,最后只有一个0,显而易见,0是这个序列的最小值,所以可以转化为询问最小值的位置。
对于三个数的询问,我们只能获得一个极差,其实没什么用,如果我们是对4个数进行询问,询问四次,在这四个数里面,一定有一个maxa 和mina [可能不止一个,但是无所谓,因为最后确定的最小值0一定是一个]
在四种情况里面,通过枚举判断就可以得到最大值和最小值的位置。之后再用这最大最小值,每次多加进来两个数进行询问,就可以得到整个序列的最大值最小值位置。【最大值的位置可能不是固定的,因为有可能有很多个最大值,但是是无所谓的,题目里面隐含的告诉了最小值只有一个,所以最后是可以满足要求的】

具体在对a b c d 四个下标的数进行询问,之后得到这里面的maxa mina的下标,首先进行预处理,得到四次询问的结果,x1 x2 x3 x4然后找到最大值,这个最大值一定是由abcd里面的maxa mina贡献出来的,遍历x1 x2 x3 x4哪两个是这个最大值,然后这两个 xi xj 所对应的两次询问 里面的重合部分 就是abcd四个位置里面的maxa mina.

int loc1,loc2;//现在能够得出来的最大值,最小值的位置。定义为全局变量。就很好处理。
void cal(int a,int b,int c,int d){
	int x1,x2,x3,x4;
	cout<<"?"<<" "<<a<<" "<<b<<" "<<c<<endl;
	// fflush(stdout);
	cin>>x1;
	cout<<"?"<<" "<<a<<" "<<b<<" "<<d<<endl;//fflush(stdout);
	cin>>x2;
	cout<<"? "<<a<<" "<<c<<" "<<d<<endl; //fflush(stdout);
	cin>>x3;
	cout<<"? "<<b<<" "<<c<<" "<<d<<endl; //fflush(stdout);
	cin>>x4;
	int maxa=0;
	if(x1>maxa) maxa=x1;
	if(x2>maxa) maxa=x2;
	if(x3>maxa) maxa=x3;
	if(x4>maxa) maxa=x4;
	if(x1==x2 && x1==maxa){
		loc1=a,loc2=b;
		return ;
	}
	if(x1==x3 && x1==maxa){
		loc1=a,loc2=c;
		return ;
	}
	if(x1==x4 && x1==maxa){
		loc1=b,loc2=c;
		return ;
	}
	if(x2==x3 && x2==maxa){
		loc1=a,loc2=d;
		return ;
	}
	if(x2==x4 && x2==maxa){
		loc1=b,loc2=d;
		return ;
	}
	if(x3==x4 && x3==maxa){
		loc1=c,loc2=d;
		return ;
	}
}
void solve(){
	int n;
	cin>>n;
	cal(1,2,3,4);
	for(int i=4;i<=n;i+=2){
		if(i+2<=n) cal(loc1,loc2,i+1,i+2);
		else{
			if(i==n) break;
			else {
				int num=0;
				for(int j=1;j<=n;j++) if(j!=loc1 && j!=loc2) {
					num=j;
					break;
				}
				cal(loc1,loc2,n,num);
			}
		}
	}
	cout<<"! "<<loc1<<" "<<loc2<<endl;
	// fflush(stdout);
}

交互题的规范问题:
必须要用endl!!!可能其他方法也可以,但是endl是自动清除缓存区,和fflush(stdout);效果一致,在使用endl的时候,fflush(stdout)甚至可以省略。

思路二:【偷窥别人的】

现在以1 2 3下标为主题,将[4,n]里面的每一个数放进来替换2,然后找所有结果里面的maxa,对应的位置替换为2,再做一次循环对3替换,这样123里面就一定有了最大值和最小值,再用一次思路一里面的四个数里面找到最大值,最小值的方法就可以了。

E

题目:
是这样的,有m个数组,每个数组里面的元素个数都是偶数,要把所有的数分为两个集合,一个‘L,一个’R.使得最后满足,首先,两个集合一模一样,其次,每个数组里面刚刚好有一半的元素在L,另一半在R。

思路:
首先判断能不能分好,记录每一个数字出现的次数,如果有奇数,肯定分不好。反之,一定可以分好。
关于为什么一定可以分好,因为举不出来相反的例子。。。。或者用欧拉回路来理解:
数组的编号是1—m,数组里面出现的数字也都赋予一个新的编号。假设是m~n.
一个数组里面出现了数x,就把数本身和所处数组的编号进行连边。【简单理解为二分图的形式】。
然后跑一边欧拉回路,从左边的集合到达右边集合的情况,把右边集合相应的数相应的位置,标记为L,从右边集合到达左边集合的,就把右边集合对应的数字的相应位置,标记为‘R。

具体一点的细节:因为数字的范围是1e9,所以首先要进行map,让数字变为1~2e5之间。
建立边的时候,数组为1的就用编号为1的点代替,数字里面为i的用i+N代替。
我们最后记录的答案的位置信息是平面的,两维的,所以建边的时候,用pair,把纵坐标的信息也存储进去。
实现的时候,记录每一个点的度cnt[x],现在要从这个点进行DFS,从tr[x] [cnt[x]] 开始,找到一个还没有被“占用”的点,也就是还没有记录在哪个集合的点,在找的过程中,用一个while循环,如果最后cnt[x]为-1了,也就是现在这个结点所连接的其他点都已经全部处理完了,那么就不需要找了。第一种是一个数组里面的所有的数字都已经分好了所属集合,第二种是现在一个数组里的数字,可能在几个数组里面都出现了,但是现在这几个数组都已经处理完毕。也就是刚好形成了回路。

因为建边是从数组名称到数组里面的数字,然后数字都是和数组建边【有向的】。可以理解为二分图,这样子在寻找的过程中我们一定是放了一个'L' 再放一个'r'.也就是一定OK的。

如果还不懂的话,可以看一下其他大佬的题解:

Codeforces Round #770(Div. 2) - Jerry_Black - 博客园 (cnblogs.com)

Fair Share (构造+欧拉回路)_构造欧拉回路_枉玊的博客-CSDN博客

代码:

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+5;
vector<pii>tr[3*N];//用pair建边,因为还要得到纵坐标信息。

vector<int>a[N],ans[N];//a数组,存储这个数字是几,ans存储最后的答案。
int m,num[2*N],cnt[3*N];//m是数组个数,num是数字出现次数,用来判断yes or no
//cnt是建图之后的每一个点的度数。
map<int,int>mp;//mp用来对数据进行离散化(好像不能叫严格的离散化,因为并不要求顺序。)
void dfs(int x);
void dfs1(int x);
	//用cnt是每一个点的度数,进行判断,如果当前点对应的边权信息tr[x][cnt[x]]所对应的存储答案的位置已经有结果了,
	//说明这个点处理过了,可以直接cnt[x]--,x的下一个儿子结点进行判断。
	//所以为什么初始值设置为-1?因为对于tr[x].push_back()的时候,首先放的位置是0.所以tr[x][0]也是有信息的,那么初始值赋值为-1
	//可以很好的实现代码的书写。
void dfs(int x){
	//dfs是对于一个数组的编号进行的dfs。
	while(cnt[x]!=-1 && ans[x][tr[x][cnt[x]].second]!=0) cnt[x]--;
	if(cnt[x]==-1) return ;
	int to=tr[x][cnt[x]].first;
	int loc=tr[x][cnt[x]].second;
	ans[x][loc]=1;//上述信息的first second 分别代表了x结点的儿子的数值大小,和对应的位置的纵坐标。
	cnt[x]--;
	dfs1(to);//相当于x找到了一条边从第一个集合到第二个集合,然后我就需要再对to这个点到找一个边从第二集合再到第一集合。
	//也就是形成回路。
}
void dfs1(int x){//dfs是对数组里面的数字进行的dfs
		while(cnt[x]!=-1 && ans[tr[x][cnt[x]].first][tr[x][cnt[x]].second]!=0) cnt[x]--;
		if(cnt[x]==-1) return ;
		int to=tr[x][cnt[x]].first;
		int loc=tr[x][cnt[x]].second;
		ans[to][loc]=2;
		cnt[x]--;
		dfs(to);
}
//其实两个dfs是可以写在一起的,只是ans记录答案时候要处理一下而已,再多一个参数,存储接下来ans要放1 还是2就可以。但是感觉这样子比较的,简单。
int main()
{
	cin>>m;
	int tot=0;
	memset(cnt,-1,sizeof(cnt));//为什么初始值不赋值为0,主要是为了方便写dfs。
	for(int i=1;i<=m;i++){
		int k;
		cin>>k;
		a[i].resize(k+1),ans[i].resize(k+1);
		for(int j=1;j<=k;j++){
			cin>>a[i][j];
			if(mp[a[i][j]]==0) mp[a[i][j]]=++tot;
			num[mp[a[i][j]]]++;
		}
	}
	for(int i=1;i<=tot;i++){
		if(num[i]%2==1){
			cout<<"NO"<<endl;
			return 0;
		}
	}
	cout<<"YES"<<endl;//以上就是进行了离散化和yes no的判断。
	for(int i=1;i<=m;i++){
	 	for(int j=1;j<a[i].size();j++){
	 		int t=mp[a[i][j]]+100000;
	 		tr[i].push_back({t,j}); cnt[i]++;
	 		tr[t].push_back({i,j}); cnt[t]++;
	 	}
	 }
	for(int i=1;i<=m;i++){
	 	if(cnt[i]!=-1) dfs(i);
	}
	for(int i=1;i<=m;i++){
	 	int k=ans[i].size();
	 	for(int j=1;j<k;j++){
	 		if(ans[i][j]==1) cout<<'L';
	 		else cout<<'R';
	 	}
	 	cout<<endl;
	}
	return 0;
}
posted @ 2023-04-15 15:32  橘赴亦梦人ω  阅读(30)  评论(0编辑  收藏  举报