很不錯的一道構造
題意
給定一個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;
}
**
