【每日一题】补档 ARC092D - Two Sequences | 思维和优化 | 困难

题目内容

原题链接

给定一个长度为 n n n 的整数数组 a a a ,一个长度为 n n n 的整数数组 b b b

问所有 a i + b j a_i+ b_j ai+bj 的异或和是多少。

数据范围

1 ≤ n ≤ 200000 1\leq n\leq 200000 1n200000
0 ≤ a i , b i < 2 28 0\leq a_i,b_i <2^{28} 0ai,bi<228

题解

思路1

考虑二进制第 k k k 位,二进制位从第 0 0 0 位开始。由于这里会产生进位,我们考虑什么情况下第 k k k 位为 1 1 1 ,即 n 2 n^2 n2 个加法结果的第 k k k 位有奇数个 1 1 1

对于第 k k k 位的结果,其必然只和前 k k k 位的加法有关。

考虑 a b i t = a i   m o d   2 k + 1 , b b i t = b j   m o d   2 k + 1 a_{bit}=a_i\bmod 2^{k+1}, b_{bit}=b_j\bmod 2^{k+1} abit=aimod2k+1,bbit=bjmod2k+1
那么 a b i t + b b i t a_{bit}+b_{bit} abit+bbit 的结果的第 k k k 位为 1 1 1 有哪些情况呢
k = 2 k=2 k=2 为例:

  • [ 010 0 2 , 011 1 2 ] → [ 2 k , 2 k + 1 − 1 ] [0100_2,0111_2]\rightarrow[2^k,2^{k+1}-1] [01002,01112][2k,2k+11]
  • [ 110 0 2 , 111 1 2 ] → [ 3 ⋅ 2 k , 2 k + 2 − 1 ] [1100_2,1111_2]\rightarrow[3\cdot 2^k,2^{k+2}-1] [11002,11112][32k,2k+21]

和在这两个区间内的一对数,其第 k k k 位为 1 1 1

所以我们考虑枚举二进制位,然后枚举 a i a_i ai ,二分答案 b j b_j bj 即可,对于 b b b 需要二分,所以就得排序,排序按照取模后的值进行排序。
时间复杂度: O ( 29 n log ⁡ n ) O(29n\log n) O(29nlogn)

思路2

考虑二分是有序的,我们可以对 a a a 也进行排序。

然后进行两次双指针,一个用来考虑第一个区间 [ 2 k , 2 k + 1 − 1 ] [2^k,2^{k+1}-1] [2k,2k+11] ,另一个用来考虑第二个区间 [ 3 ⋅ 2 k , 2 k + 2 − 1 ] [3\cdot 2^k,2^{k+2}-1] [32k,2k+21]

但是由于排序的限制,时间复杂度不变。

时间复杂度: O ( 29 n log ⁡ n ) O(29n\log n) O(29nlogn)

思路3

现在瓶颈在于排序,一般来说,涉及到的排序问题是无法进行优化的。

但是这里不同,这里我们一个条件是需要枚举二进制位,并且从小到大枚举。

那么就是天然适配基数排序的,在这里,基数排序的复杂度被均摊到了每一个二进制位枚举中。

所以优化后时间复杂度为: O ( 29 n ) O(29n) O(29n)

代码1

#include 
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<int> a(n), b(n);
    for (int i = 0; i < n; ++i) cin >> a[i];
    for (int i = 0; i < n; ++i) cin >> b[i];

    auto bs1 = [&](int ai, int p, int x) {
        int l = 0, r = n;
        while (l < r) {
            int mid = (l + r) >> 1;
            if ((ai & p) + (b[mid] & p) >= x) r = mid;
            else l = mid + 1;
        }
        return l;
    };

    auto bs2 = [&](int ai, int p, int x) {
        int l = 0, r = n;
        while (l < r) {
            int mid = (l + r) >> 1;
            if ((ai & p) + (b[mid] & p) > x) r = mid;
            else l = mid + 1;
        }
        return l;
    };

    auto count = [&](int p, int minv, int maxv) {
        // 01000 ~ 01111
        int c = 0;
        for (int i = 0; i < n; ++i) {
            int L = bs1(a[i], p, minv);
            if (L != n) {
                int R = bs2(a[i], p, maxv);
                if ((R - L) & 1) c += 1;
            }
        }
        return c & 1;
    };

    int ans = 0;
    for (int j = 1; j <= 30; ++j) {
        int mod = (1 << j) - 1;
        sort(b.begin(), b.end(), [&](const int x, const int y) {
            return (x & mod) < (y & mod);
        });

        // minv = 01000... ~ 01111...
        int minv = 1 << (j - 1);
        int maxv = (1 << j) - 1;
        int cnt = count(mod, minv, maxv);

        // minv = 11000... ~ 11111...
        minv = (1 << (j - 1))+ (1 << j);
        maxv = (1 << (j + 1)) - 1;
        cnt += count(mod, minv, maxv);

        if (cnt & 1) {
            ans |= 1 << (j - 1);
        }
    }

    cout << ans << "\n";

    return 0;
}

代码2

#include 
using namespace std;

const int MAX = 1000000;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    vector<int> a(n);
    vector<int> b(n);

    for (int i = 0; i < n; ++i) cin >> a[i];
    for (int i = 0; i < n; ++i) cin >> b[i];

    int ans = 0;
    for (int k = 0; k < 29; ++k) {
        int mask = (1 << (k + 1)) - 1;
        sort(a.begin(), a.end(), [&](const int x, const int y) { return (x & mask) < (y & mask); });
        sort(b.begin(), b.end(), [&](const int x, const int y) { return (x & mask) < (y & mask); });
        int l1 = n - 1, r1 = n - 1, l2 = n - 1, r2 = n - 1;
        int p = 1 << k, q = (1 << (k + 1)) - 1, r = 3 << k, s = (1 << (k + 2)) - 1;

        int cnt = 0;
        for (int i = 0; i < n; ++i) {
            while (l1 >= 0 && ((a[i] & mask) + (b[l1] & mask)) >= p) l1 -= 1;
            while (r1 >= 0 && ((a[i] & mask) + (b[r1] & mask)) > q) r1 -= 1;
            while (l2 >= 0 && ((a[i] & mask) + (b[l2] & mask)) >= r) l2 -= 1;
            while (r2 >= 0 && ((a[i] & mask) + (b[r2] & mask)) > s) r2 -= 1;
            cnt = (cnt + (r1 - l1) + (r2 - l2)) & 1;
        }
        if (cnt) ans |= 1 << k;
    }

    cout << ans << "\n";

    return 0;
}

代码3

#include 
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<int> a(n), b(n);
    for (int i = 0; i < n; ++i) cin >> a[i];
    for (int i = 0; i < n; ++i) cin >> b[i];

    int ans = 0;
    vector<vector<int>> nums(2, vector<int>(n));
    auto mySort = [&](int k, vector<int>& vec) {
        int c[2] = {-1, -1};
        for (auto u: vec) {
            int bit = u >> k & 1;
            nums[bit][++c[bit]] = u;
        }
        for (int i = 0, j = 0; i < 2; ++i) {
            for (int t = 0; t <= c[i]; ++t) {
                vec[j] = nums[i][t];
                j++;
            }
        }
    };

    for (int k = 0; k < 29; ++k) {
        int mask = (1 << (k + 1)) - 1;

        // sort 转换为基数排序
        mySort(k, a);
        mySort(k, b);

        int cnt = 0;
        int i = n - 1, j = n - 1, p = n - 1, q = n - 1;
        int l1 = 1 << k, r1 = (1 << (k + 1)) - 1, l2 = 3 << k, r2 = (1 << (k + 2)) - 1;
        for (int ai: a) {
            while (i >= 0 && (ai & mask) + (b[i] & mask) >= l1) i--;
            while (j >= 0 && (ai & mask) + (b[j] & mask) > r1) j--;
            while (p >= 0 && (ai & mask) + (b[p] & mask) >= l2) p--;
            while (q >= 0 && (ai & mask) + (b[q] & mask) > r2) r2 -= 1;
            cnt += (j - i) + (n - 1 - p);
        }
        if (cnt & 1) ans |= 1 << k;
    }

    cout << ans << "\n";

    return 0;
}

你可能感兴趣的:(算法竞赛,算法,思维)