Codeforces 1516B AGAGA XOOORRR
题目链接: http://codeforces.com/problemset/problem/1516/B
题意
一个含有 n 个非负数的数组,定义某种操作可以把相邻的两个数通过 XOR 合并为一个数,即每次操作后数组的元素个数都会减 1。问是否可以经过若干次这样的操作使得数组中的元素都相等?
思路
首先需要知道 XOR 操作一些的性质(^ 表示 XOR):
归零律:a ^ a = 0
恒等律:a ^ 0 = a
交换律:a ^ b = b ^ a
结合律:a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
且若有 a ^ b ^ c = d 则有 a = d ^ b ^ c
解析
假设经过若干操作后,数组中的元素都相等。那么可以分两种情况考虑,一种是数组中元素的个数为偶数,一种是奇数。其中偶数的时候一定是可以化简为 2 个元素,奇数的时候一定可以化简为 3 个元素。可以假设操作后有 m 个相等的元素,如果 m 是偶数,那么可以通过 XOR 前 m - 2 个元素使得其为 0,遂只剩下最后 2 个元素。如果 m 是奇数,那么前 m - 2 个元素 XOR 后仅剩 1 个元素,遂加上最后 2 个元素共 3 个元素,举例:
3 ^ 3 ^ 3 ^ 3 = 0 ^ 3 ^ 3 = 3 ^ 3 (偶数)
3 ^ 3 ^ 3 ^ 3 ^ 3 = 3 ^ 3 ^ 3(奇数)
2 个元素相等
如果最后剩 2 个相等的数,因为 a ^ a = 0,所以原始数组中所有元素 XOR 后的值必须为 0。
3 个元素相等
如果最后剩 3 个元素相等,即通过 2 个分割点把数组分为 3 部分,这 3 个部分的 XOR 值必须相等。那么可以通过暴力枚举分割点的方式,把原数组分割为三部分,分别计算每部分的 XOR 值。假设分割点分别为 i 和 j,每部分的 XOR 值分别为 a , b, c,XOR[i ... j] 表示 ai ^ a(i+1) ^ ... ^ aj
a = XOR[0 ... i]
b = XOR[i + 1 ... j]
c = XOR[j + 1 ... n - 1]
为了计算 a, b, c 可以计算一个前缀 XOR 数组 pre[i] 代表 XOR[0 .... i],且因为 a ^ b ^ c = d 可以得到 c = d ^ b ^ a, 所以
a = XOR[0 .. i] = pre[i]
b = XOR[i + 1 ... j] = pre[j] ^ pre[i]
c = XOR[j + 1 ... n - 1] = pre[n - 1] ^ pre[j]
如果不满足上述的情况,则说明无法将原数组按照此中操作约简为所有元素都相等数组。
代码
时间复杂度 O(N^2)
#include "bits/stdc++.h"
#define LL long long
#define LLFmt "%lld"
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define SWAP(x, y) ({ int t = x; x = y; y = t; })
#define fir first
#define sec second
#define pb push_back
const int MAX = 0x3f3f3f3f;
const int MAXN = 2e3 + 3;
using namespace std;
int main () {
int T, n; cin >> T;
while (T--) {
int p[MAXN] = { 0 }, a[MAXN] = { 0 }, k = 0, f = 0;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i]; k ^= a[i], p[i] = k;
}
if (!k) f = 1;
for (int i = 0; i < n - 2; i++) {
for (int j = i + 1; j < n - 1; j++) {
f |= (p[i] == (p[j] ^ p[i]) && p[i] == (p[n - 1] ^ p[j]));
}
}
cout << (f ? "YES" : "NO") << endl;
}
return 0;
}
扩展
上面判断 3 部分 XOR 相等的时间复杂度为 O(N^2),假设题目加了一个条件,问是否至少可以分为 k 段,那么枚举 k 部分的时间复杂度是巨大的。所有其实这里还有种 O(N) 的方法。
解析
假设需要分为 k 部分,每部分的 XOR 值都为 x, 且数组中所有元素的 XOR 值设为 xr, 即 XOR[0 ... n -1 ] = xr,那么有
x ^ x ^ x ^ ... ^ x ^ x = xr (xr 不为 0, 且 x 的数量 k 为奇数)
即有:
0 ^ x = xr
所以可以从前往后遍历数组,并记录当前的 XOR 值,每当值等于 XOR 时,次数加 1,并重新计算 XOR 值。当遍历完时,判断次数是否大于 k,如果不是则说明无法通过 XOR 将原数组化为 k 部分相等的元素,时间复杂度 O(N)。
代码
#include "bits/stdc++.h"
#define LL long long
#define LLFmt "%lld"
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define SWAP(x, y) ({ int t = x; x = y; y = t; })
#define fir first
#define sec second
#define pb push_back
#define ios ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
const int MAX = 0x3f3f3f3f;
const int MAXN = 2e3 + 3;
using namespace std;
inline int read () {
int f = 1,x = 0, ch = getchar();
while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
inline LL readll () {
LL f = 1, x = 0; int ch = getchar();
while(!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int main () {
ios; int T = read();
while (T--) {
int n = read (), a[MAXN] = { 0 }, xr = 0, cnt = 0, sg = 0;
for (int i = 0; i < n; i++) a[i] = read(), xr ^= a[i];
for (int i = 0; i < n; i++) {
sg ^= a[i];
if (sg == xr) cnt++, sg = 0;
}
cout << (xr == 0 || cnt > 2 ? "YES" : "NO") << endl;
}
return 0;
}