Title

CF1572B Xor of 3 题解

解题思路

首先我们知道,当序列中有奇数个 \(1\) 的时候无解,因为每次操作只会增加或减少 \(2\)\(0\)\(1\),当序列中有奇数个 \(1\) 的时候,一定会存在一个 \(1\) 无法被消除。因此,在这种情况下,直接输出 NO 即可。

不难发现,序列的异或和为一定值,证明如下:

  • \(sx\) 表示每一次操作选取的三个数的异或和,令 \(s\) 表示整个序列的异或和;
  • 每次操作可以看作删去三个数、添加三个数,不妨设这三个数分别为 \(x_1,x_2,x_3\),那么在删去三个数的时候,\(s\gets s\oplus (x_1\oplus x_2\oplus x_3)\),替换一下可得 \(s\gets s\oplus sx\),新添入三个 \(sx\)\(s\gets (s\oplus sx)\oplus sx\oplus sx\oplus sx\),打开括号后可得 \(s\gets s\),异或和不变。

显然直接从左往右扫一次是可以更新形如 01111010111 的段的,但是序列中显然还可能存在形如 01000010 的段,这种情况下直接扫描是无法更新的。我们考虑将后面的段转化为前面的段。由于每次操作最多会产生 \(2\) 的影响,我们可以每次枚举 \(i=2\cdot k+1,k\in \mathbb N\) 的位置,这样对于最终的答案是没有影响的。由于序列前面某一段可能需要倒序更新,而后面的段只需要直接暴力修改,那么我们考虑找到第一段需要倒序修改的段,由于从 \(1\) 开始的一段连续的 \(0\) 不管是否修改都没有影响,那么一段需要修改的段一定不能只包括 \(0\)。同理,由一段中需要有偶数个 \(1\) 才能被消除,那么第一段中也应该包含偶数个 \(1\)。由此,我们需要找到第一段包含偶数个 \(1\) 的且右端为奇数的段,直接枚举即可。

在全部转化后,再从左往右扫一次就可以得到答案了。

总共需要 \(O(n)\) 次操作,可以通过本题。

AC 代码

#include<vector>
#include<stdio.h>
#include<stdlib.h>
#define N 200005
int n,a[N],sum[N];
inline void work(){
    scanf("%d",&n);std::vector<int> ans;int _1=0;
    for(register int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(register int i=1;i<=n;++i) if(a[i]==1) ++_1;
    if(_1&1){puts("NO");return;}
    for(register int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
    int pos=0;for(register int i=1;i<=n;++i)
        if((i&1)&&sum[i]%2==0){pos=i;break;}
    if(!pos){puts("NO");return;}
    for(register int i=pos-2;i>=1;i-=2) ans.push_back(i);
    for(register int i=pos+1;i<=n-2;i+=2) ans.push_back(i);
    for(register int i=1;i<=n-2;i+=2) ans.push_back(i);
    puts("YES"); printf("%d\n",ans.size()); if(ans.empty()) return;
    for(auto now:ans) printf("%d ",now); putchar('\n');
}signed main(){int T;scanf("%d",&T);while(T--) work();}
posted @ 2024-02-07 10:00  UncleSam_Died  阅读(9)  评论(0)    收藏  举报