CF1573D D. Xor of 3 | 2500
很不错的一道构造
题意
给定一个01序列, 每次可以选择相邻的三个数, 将他们异或起来, 并让这三个数都等于异或结果。
每次操作用一个数x表示, 表示将x, x+1, x+2 异或起来
求一个长度不超过n的操作序列使得序列中每个数都等于0, 或者输出无解
保证只要能让所有数都等于0, 则存在合法操作序列
题解
首先我们考虑有奇数个1, 那么显然无解
这时候我们考虑从第一个1开始, 每次消掉第一对1(最前面两个1)
--这时候对于每一对1分两种情况
-- · 中间有奇数个0
比如10001、101 , 这种可以直接消掉
假设第一个1位置为x, 那么可以进行x, x+2, x+4....., 使得变成中间只有一个0
10001 -> 11101
然后消掉最后三个, 再一直往前消即可。
-- · 中间有偶数个0
这种显然无法直接整个消掉, 可以让他先全部变成1, 这时候如果左边或右边有一个0, 就可以消掉, 否则无解
比如1001 -> 1111 假设 原序列是10010, 右边有一个0, 那么现在11110可以消掉
实现
还是不好实现的,这里给出我的实现
如果第一个数是0, 直接往后消, 奇数直接消, 偶数前面有0能消
否则第一个数是1, 如果第一对是奇数情况, 直接消掉, 变第一种情况
否则第一对是偶数, 暂时不管第一对, 先把后面的消了, 这时候后面就有了0, 再消第一对
如果后面消不掉, 那么显然无解
具体不懂看代码
solve(x) 表示将x及以后的全消掉, return1表示能消掉, 否则不能.
**#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int read(){
int num=0, flag=1; char c=getchar();
while(!isdigit(c) && c!='-') c=getchar();
if(c=='-') flag=-1, c=getchar();
while(isdigit(c)) num=num*10+c-'0', c=getchar();
return num;
}
const int N = 200505;
int T, n;
int a[N], nex[N];
vector<int> ans;
int solve(int x){
if(x > n) return 0;
if(nex[x] == 0) return 1;
if( a[x] == 0 || (nex[x]-x-1)%2 ){
if(a[x]==0) x = nex[x];
while(x){
if((nex[x]-x-1)%2){
int i;
for(i=x; i+2<nex[x]; i+=2){
ans.push_back(i);
}
for(; i>=x; i-=2){
ans.push_back(i);
}
}else{
for(int i=x; i+2<nex[x]; i+=2){
ans.push_back(i);
}
for(int i=x-1; i+2<=nex[x]; i+=2){
ans.push_back(i);
}
}
x = nex[nex[x]];
}return 1;
}else{
if(solve(nex[x]+1)){
for(int i=x; i+2<nex[x]; i+=2){
ans.push_back(i);
}
for(int i=nex[x]+1; i-2>=x; i-=2){
ans.push_back(i-2);
}
return 1;
}else return 0;
}
}
int main(){
T=read();
while(T--){
n = read();
for(int i=1; i<=n; i++) a[i] = read();
int las = 0;
int cnt = 0;
for(int i=n; i>=1; i--){
nex[i] = las;
if(a[i]) las = i, cnt++;
}
if(cnt % 2) {
printf("NO\n");
continue;
}
ans.clear();
if(solve(1)){
printf("YES\n%d\n", ans.size());
for(int i=0; i<ans.size(); i++) {
printf("%d ", ans[i]);
}printf("\n");
} else printf("NO\n");
}
return 0;
}
**