2024牛客暑期多校训练营1 D.XOR of Suffix Sums(思维+二进制)

原题链接:D.XOR of Suffix Sums


题目大意:


给出一个最初为空的数组 a a a,同时再给出 q q q 次操作。

每次操作给出两个数字 t , v t,v t,v,表示先从数组末尾删除连续 t t t 个数字,在向末尾加入一个数字 v v v,保证 v v v 为非负整数。

操作完之后,回答对于所有后缀和的异或值为多少。

即假设当前数组长度为 n n n,且 i i i 的后缀和为 S u f i = a i + a i + 1 + . . . + a n Suf_{i}=a_{i} +a_{i+1}+...+a_{n} Sufi=ai+ai+1+...+an

那么此时询问的答案即 S u f 1 ⊕ S u f 2 ⊕ . . . ⊕ S u f n Suf_{1} \oplus Suf_{2} \oplus... \oplus Suf_{n} Suf1Suf2...Sufn ,答案对 2 21 2^{21} 221 取模。

解题思路:


每次操作即删除末尾若干个整数,再加入一个整数,删除操作肯定是添加操作量级的,这个好做,考虑如何求得答案。

设前缀和数组为 S S S,那么 S u f i = S n − S i − 1 Suf_{i}= S_{n}-S_{i-1} Sufi=SnSi1

那么问题就转化成了 ⊕ i = 1 n S n − S i − 1 \oplus_{i=1}^{n}S_{n}-S_{i-1} i=1nSnSi1

考虑异或最基础的求答案方法,某个位上 1 1 1 的总数为偶数则为 0 0 0,为奇数则为 1 1 1

那么我们对 2 20 2^{20} 220 次方的每个位分别统计 1 1 1 的个数即可。

我们把问题拆成 2 0 , 2 1 , . . . , 2 20 2^{0},2^{1},...,2^{20} 20,21,...,220 然后分别考虑某一个位置上 0 / 1 0/1 0/1 的变化。

我们要知道有多少个 i i i 满足 S u f i Suf_{i} Sufi 在第 k k k 位上有 1 1 1
即我们要找到有多少个 − S i − 1 + S n m o d    2 k + 1 ≥ 2 k -S_{i-1}+S_{n} \mod 2^{k+1} \ge 2^{k} Si1+Snmod2k+12k 就行了(此时第 k k k 位上一定会是 1 1 1)。

那么问题就转化为找模意义下的 2 k − S n ≤ − S i − 1 ≤ ( 2 k − S n ) + 2 k − 1 2^{k}-S_{n} \leq -S_{i-1} \leq (2^{k}-S_{n}) + 2^{k}-1 2kSnSi1(2kSn)+2k1 i i i 的数量就是有多少个 S n − S i − 1 S_{n}-S_{i-1} SnSi1 k k k 位为 1 1 1 i i i 的数量(这里需要理解一下)。

注意这里的 l = 2 k − S n , r = ( 2 k − S n ) + 2 k − 1 l=2^{k}-S_{n},r=(2^{k} - S_{n}) + 2^{k}-1 l=2kSn,r=(2kSn)+2k1 由于存在 m o d    2 k + 1 \mod 2^{k+1} mod2k+1 的原因可能存在 l > r l>r l>r,此时询问的细节要注意一下变成询问区间 [ 0 , r ] [0,r] [0,r] 和区间 [ l , 2 k + 1 − 1 ] [l,2^{k+1}-1] [l,2k+11]

那么实现方面我们就是要查询值域 [ l , r ] [l,r] [l,r] 范围内的数字个数的和,还要支持加删改,显然这里树状数组是最方便的。

我们要对每个 k k k 分别维护一个树状数组,假设 V V V 为值域则需要 O ( log ⁡ V ) O(\log V) O(logV) 棵树,每棵树询问的复杂度为 O ( log ⁡ V ) O(\log V) O(logV) 那么单次操作的复杂度为 O ( log ⁡ 2 V ) O(\log^{2} V) O(log2V) 可以通过。

细节挺多的,写的时候注意一些即可。

时间复杂度: O ( q log ⁡ 2 V ) O(q \log^{2} V) O(qlog2V)

#include <bits/stdc++.h>

using i64 = long long;

template<typename T>
struct BIT {
    const int n;
    std::vector<T> tree;
    BIT(int n) : n(n), tree(n + 1) {};
    T Query(int x) {
        T res = 0;
        for (int i = x; i > 0; i -= (i & -i)) {
            res += tree[i];
        }
        return res;
    }
    void Modify(int l, T z) {
        if (l <= 0) return;
        for (int i = l; i <= n; i += (i & -i)) {
            tree[i] += z;
        }
    }
    T rangeQuery(int l, int r) {
        return Query(std::min(n, r)) - Query(std::max(0, l - 1));
    }
};

void solve() {
    int q;
    std::cin >> q;

    const int N = 21, mod = 1 << 21;

    int ans = 0;

    std::vector<int> A(1);

    //这里我的树状数组区间为 [1, n] 查询时将 [0, n - 1] 变成 [1, n] 即可
    std::vector BitCnt(N, BIT<int>(1 << N));

    //k进制下的S[n]
    std::vector<i64> S(N);

    auto query = [&](int k) -> int {
        const int v = 1 << k, m = 2 << k;
        int l = ((v - S[k]) % m + m) % m;
        l = (l == 0 ? m : l);
        int r = ((l + v - 1) % m + m) % m;
        r = (r == 0 ? m : r);
        if (l <= r) {
            return BitCnt[k].rangeQuery(l, r) % 2;
        } else {
            return (BitCnt[k].rangeQuery(1, r) % 2) ^ (BitCnt[k].rangeQuery(l, m) % 2);
        }
    };

    //一定要注意add 和 del的区别
    auto add = [&](int v) -> void {
        A.emplace_back(v % mod);
        for (int i = 0; i < N; ++i) {
            ans ^= query(i) << i;
            int m = (2 << i);
            int x = (m - S[i]) % m;
            x = (x == 0 ? m : x);
            BitCnt[i].Modify(x, 1);
            S[i] = (S[i] + A.back()) % m;
            ans ^= query(i) << i;
        }
    };

    auto del = [&]() -> void {
        for (int i = 0; i < N; ++i) {
            ans ^= query(i) << i;
            int m = (2 << i);
            S[i] = (S[i] - A.back() % m + m) % m;
            int x = (m - S[i]) % m;
            x = (x == 0 ? m : x);
            BitCnt[i].Modify(x, -1);
            ans ^= query(i) << i;
        }
        A.pop_back();
    };

    for (int i = 1; i <= q; ++i) {
        int t, v;
        std::cin >> t >> v;
        for (int j = 0; j < t; ++j) {
            del();
        }
        add(v);

        std::cout << ans << "\n";
    }
}

signed main() {

    std::ios::sync_with_stdio(0);
    std::cin.tie(0);

    int t = 1;
    //std::cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值