码迷,mamicode.com
首页 > 其他好文 > 详细

Codeforces 1516B AGAGA XOOORRR

时间:2021-04-24 13:14:41      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:相等   adl   end   遍历   fir   push   class   while   lld   

题目链接: 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;
}

Codeforces 1516B AGAGA XOOORRR

标签:相等   adl   end   遍历   fir   push   class   while   lld   

原文地址:https://www.cnblogs.com/Ash-ly/p/14694664.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!