三个数进行异或,三个数和的奇偶性不变。或者说三个数,任意一位上 1 1 1 的个数的奇偶性不变。也就是说,每次操作后, n n n 个数每一位上的 1 1 1 的个数的数量要么不变 (±0),要么 ± 2 ±2 ±2
如果 n n n 是奇数,对于第 k k k 位,如果这一位上 1 1 1 的数量是奇数,那剩下 0 0 0 的数量就是偶数,我们最终只能通过增加偶数个 1 1 1 把 n n n 个数的第 k k k 位都置为 1 1 1; 1 1 1 的数量为偶数也同理,就只能把 n n n 个数的第 k k k 位都置为 0 0 0。也就是说, n n n 为奇数一定有解,而且最终这相同的 n n n 个数等于最初这 n n n 个数的异或和。
偶数也是一样分析,如果有某一位上 1 1 1 的个数为奇数就无解,这种情况下所有数的异或和也肯定不为 0 0 0。不然就有解,不过解不唯一,因为我可以去掉偶数个 1 1 1,也可以补齐偶数个 1 1 1。
官方题解就很妙:首先对形如
a
b
b
a\ b\ b
a b b 的数进行异或,得到的是
a
a
a。然后如果是奇数,就是
{
1
,
2
,
3
}
,
{
3
,
4
,
5
}
,
.
.
.
,
{
k
−
2
,
k
−
1
,
k
}
{
k
,
k
+
1
,
k
+
2
}
\{ 1,2,3 \},\{ 3,4,5 \},...,\{ k-2,k-1,k \}\{ k,k+1,k+2 \}
{1,2,3},{3,4,5},...,{k−2,k−1,k}{k,k+1,k+2} 这样进行操作,一共
n
3
\frac{n}{3}
3n 次…哦好像是
n
−
1
2
\frac{n-1}{2}
2n−1 次,由于是奇数所以最后一次操作一定是
{
n
−
2
,
n
−
1
,
n
}
\{ n-2,n-1,n \}
{n−2,n−1,n}。然后,由于每次都是“叠”着上一次操作的第三个数,这样可以把前面的数的异或和“传递”给下一组三元组,这样最后一次操作后得到的三个数,就是
n
n
n 个数的异或和。接下来,数组大概变成这样子:
{
a
,
a
,
b
,
b
,
c
,
c
.
.
.
m
,
m
,
n
,
n
,
n
}
\{ a,a,b,b,c,c...m,m,n,n,n \}
{a,a,b,b,c,c...m,m,n,n,n},就是用
a
b
b
a\ b\ b
a b b 的形式了。
如果是偶数,先判断
n
n
n 个数的异或和是否为
0
0
0,然后对前
n
−
1
n-1
n−1 个数进行操作,和奇数一样,最后一个数:
To solve for even n and X=0, we can just solve the problem for the first n−1 using the odd approach and the last element will magically be equal to the first n−1. ——(来自官方题解)
struct Triple {
int x,y,z;
Triple(int a=0,int b=0,int c=0){ x=a; y=b; z=c; }
};
vector<Triple>ans;
int a[200100];
void solve(int n) {
for(int i=1;i+2<=n;i+=2) {
ans.push_back( Triple(i,i+1,i+2) );
}
for(int i=1;i<n;i+=2) {
ans.push_back( Triple(i,i+1,n) );
}
}
int main() {
int T = 1;
// scanf("%d",&T);
while(T--) {
int n, m;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i);
int XOR = 0;
for(int i=1;i<=n;i++) XOR = XOR ^ a[i];
if( n&1 ) {
solve(n);
}
else {
if( XOR != 0 ){ printf("NO\n"); }
else solve(n-1);
}
if( !ans.empty() ) {
printf("YES\n");
printf("%d\n",ans.size());
for(auto [x,y,z] : ans)printf("%d %d %d\n",x,y,z);
printf("\n");
}
}
}