【题解(异或拆位 trick)】GDKOI2016 魔卡少女 + CF242E XOR on Segment


GDKOI2016 魔卡少女 + CF242E XOR on Segment

是两道题的题解

这两道题都用了异或拆位的套路

GDKOI2016 魔卡少女

题面

给定一个长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\)\(m\) 次操作,操作内容如下

  1. 给定 \(p, x\),将 \(a_p\) 修改为 \(x\)
  2. 给定 \(l, r\),询问区间 \([l, r]\)魔法效果

其中,魔法效果的定义为:\([l, r]\)所有 连续子序列的异或和 之和。

\(n,m\le 10^5~,~0\le a_i,x\le10^3\)

没读懂题?查看原题面。

题解

注意到值域很小,\(0\le a_i\le 1000\),对应的二进制最多有 \(10\) 位(\(2^{10}=1024>1000\)),考虑对所有 \(10\) 个二进制位维护一颗线段树。

对于修改,由于异或每一位的运算独立,所以对于 \(x\) 二进制下的所有位单独修改即可。

对于询问,我们考虑从所有 \(10\) 个二进制位,最终答案是所有二进制位的贡献之和(这是因为位与位之间独立,即不进位)。

对于第 \(i\) 个二进制位,贡献为 \(2^i\times num\),其中 \(num\) 表示 \([l, r]\) 中所有异或和 \(=1\) 的子区间的数量。

  • 理解:仅考虑第 \(i\) 位,所有位置上的数 \(\in \{0, 1\}\),所以所有子区间异或和 \(\in\{0, 1\}\)。联系题目所求,可以知道对于异或和为 \(1\) 的子区间,贡献为 \(1\times 2^i\)(二进制下第 \(i\) 位实际为 \(2^i\));异或和为 \(0\) 的没有贡献。所以第 \(i\) 位的贡献位 \(2^i\times num\)

考虑怎么在线段树上维护子区间异或和为 \(1\) 的数量,我们可以维护这些值

  1. \(l[0/1]\):表示连续子序列从左端点(不包含右端点)开始异或和为 \(0/1\) 的子序列数量。

  2. \(r[0/1]\):表示连续子序列从右端点(不包含左点)开始异或和为 \(0/1\) 的子序列数量。

  3. \(sum\):区间异或和。

  4. \(num\):即上文的”子区间异或和为 \(1\) 的数量“。

转移如下

// Node 是线段树上的一个结点对应的结构体
friend Node operator + (Node lhs, Node rhs) { // lhs左区间,rhs右区间
    Node res;
    res.sum = lhs.sum ^ rhs.sum;
    res.l[0] = lhs.l[0] + rhs.l[lhs.sum]; // "+ rhs.l[lhs.sum]"解释:如果lhs异或和为1,那么rhs也为1,异或起来才是0
    res.l[1] = lhs.l[1] + rhs.l[lhs.sum ^ 1]; // 同上,反之亦然
    res.r[0] = rhs.r[0] + lhs.r[rhs.sum]; // 同理
    res.r[1] = rhs.r[1] + lhs.r[rhs.sum ^ 1]; // 同理
    res.ans = lhs.ans + rhs.ans + 1ll * lhs.r[0] * rhs.l[1] + 1ll * lhs.r[1] * rhs.l[0]; // "+ 1ll * lhs.r[0] * rhs.l[1]"解释:lhs右边异或和为0,rhs左边异或和为1,总异或和为1
    return res;
}

然后就做完了,时间和空间复杂度都是 \(O(n\log{n}\log{a_i})\)

总结:异或拆位套路

  • 异或位与位之间独立,所以可以拆开处理。
  • 值域不能太大,否则线段树开不下,时间也不太能过。
  • 考虑每一位贡献的时候,记得 \(\times 2^i\)

代码

点击查看代码
#include
using namespace std;
using i64 = long long;
const int N = 1E5 + 5;
const i64 P = 1E8 + 7; 
int n, m;
struct Node {
    int l[2], r[2];
    i64 sum, num; // sum是异或和,num是所有连续子区间异或为1的数量 
    Node() { l[0] = l[1] = r[0] = r[1] = sum = num = 0; }
    Node(int x) {
        l[1] = r[1] = sum = num = x;
        l[0] = r[0] = x ^ 1;
    } 
    friend Node operator + (Node lhs, Node rhs) {
        Node res;
        res.sum = lhs.sum ^ rhs.sum;
        res.l[0] = lhs.l[0] + rhs.l[lhs.sum];
        res.l[1] = lhs.l[1] + rhs.l[lhs.sum ^ 1];
        res.r[0] = rhs.r[0] + lhs.r[rhs.sum];
        res.r[1] = rhs.r[1] + lhs.r[rhs.sum ^ 1];
        res.num = lhs.num + rhs.num + 1ll * lhs.r[0] * rhs.l[1] + 1ll * lhs.r[1] * rhs.l[0];
        return res;
    }
};
struct SegementTree {
    #define lson (rt << 1)
    #define rson (rt << 1 | 1)
    #define mid ((l + r) >> 1)
    Node t[N * 4];
    void update(int rt, int l, int r, int x, int val) {
        if(l == r) {
            t[rt] = Node(val);
            return;
        }
        if(x <= mid) {
            update(lson, l, mid, x, val); 
        } else {
            update(rson, mid + 1, r, x, val);
        }
        t[rt] = t[lson] + t[rson];
    }
    Node query(int rt, int l, int r, int L, int R) {
        if(L <= l && r <= R) {
            return t[rt];
        }
        Node res;
        if(L <= mid) {
            res = res + query(lson, l, mid, L, R);
        }
        if(R > mid) {
            res = res + query(rson, mid + 1, r, L, R);
        }
        return res;
    }
    #undef mid
} seg[10];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for(int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        for(int j = 0; j < 10; ++j) {
            seg[j].update(1, 1, n, i, (x >> j) & 1);
        }
    }
    cin >> m;
    while(m--) {
        char opt[10];
        cin >> opt;
        if(opt[0] == 'Q') {
            int l, r;
            cin >> l >> r;
            i64 num = 0;
            for(int j = 0; j < 10 ; ++j) {
                Node now = seg[j].query(1, 1, n, l, r);
                num += now.num << j;
            }
            cout << num % P << "\n";
        } else {
            int x, val;
            cin >> x >> val;
            for(int j = 0; j < 10; ++j) {
                seg[j].update(1, 1, n, x, (val >> j) & 1);
            }
        }
    }
    return 0;
}

CF242E XOR on Segment

其实比上一题还简单一些,可以当作练习。

题面

给定长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\)。有 \(m\) 次操作,操作内容如下:

  1. 给定 \(l, r\),求 \(\sum\limits_{i=l}^ra_i\)
  2. 给定 \(l, r, x\),将 \(a_l\) ~ \(a_r\) 异或上 \(x\)

\(n\le 10^5~,~m\le 5\times10^4~,~0\le a_i,x\le 10^6\)

题解

由于修改操作是区间异或,我们联想到了刚才的拆位。\(\log_2{a_i}\le 20\),需要拆成 \(20\) 位,感觉可以接受。

对于修改,相当于每一位区间异或 \(0/1\),考虑用线段树维护区间和。

维护这个比上一题简单多了,统计区间内 \(0\)\(1\) 的数量,根据异或值的不同更新这两个值即可。

询问区间和相信大家都会,此处略去。时间和空间复杂度都是 \(O(n\log{n}\log{a_i})\)

代码

点击查看代码
#include
using namespace std;
using i64 = long long;
const int N = 1E5 + 5;
const int M = 5E4 + 4;
int n, m;
struct SegmentTree {
    #define lson (rt << 1)
    #define rson (rt << 1 | 1)
    #define mid ((l + r) / 2) 
    i64 sum[N * 4][2], tag[N * 4];
    void pushdown(int rt, int l, int r) {
        if(tag[rt] != -1) {
            tag[lson] = tag[lson] == -1 ? tag[rt] : tag[lson] ^ tag[rt];
            tag[rson] = tag[rson] == -1 ? tag[rt] : tag[rson] ^ tag[rt];
            // [l, mid] 01 更新 
            int one, zero;
            one = sum[lson][tag[rt] ^ 1];
            zero = sum[lson][tag[rt]];
            sum[lson][0] = zero;
            sum[lson][1] = one;
            // [mid + 1, r] 01 更新 
            one = sum[rson][tag[rt] ^ 1];
            zero = sum[rson][tag[rt]];
            sum[rson][0] = zero;
            sum[rson][1] = one;
            // -1 为 tag[rt] 标记空值 
            tag[rt] = -1;
        }
    }
    void pushup(int rt) {
        sum[rt][0] = sum[lson][0] + sum[rson][0]; 
        sum[rt][1] = sum[lson][1] + sum[rson][1];
    }
    void build(int rt, int l, int r) {
        tag[rt] = -1;
        sum[rt][0] = r - l + 1; // 一开始全部赋值为 0
        if(l == r) {
            return;
        }
        build(lson, l, mid);
        build(rson, mid + 1, r);
        pushup(rt);
    }
    void update(int rt, int l, int r, int L, int R, i64 val) {
        if(L <= l && r <= R) {
            tag[rt] = tag[rt] == -1 ? val : tag[rt] ^ val;
            // [l, r] 01 更新 
            int one = sum[rt][val ^ 1], zero = sum[rt][val];
            sum[rt][0] = zero;
            sum[rt][1] = one;
            return;
        } 
        pushdown(rt, l, r); 
        if(L <= mid) {
            update(lson, l, mid, L, R, val);
        }
        if(R > mid) {
            update(rson, mid + 1, r, L, R, val);
        }
        pushup(rt);
    }
    int query(int rt, int l, int r, int L, int R) {
        if(L <= l && r <= R) {
            return sum[rt][1];
        }
        pushdown(rt, l, r);
        int res = 0;
        if(L <= mid) {
            res += query(lson, l, mid, L, R);
        } 
        if(R > mid) {
            res += query(rson, mid + 1, r, L, R);
        }
        return res;
    }
    #undef mid 
} seg[20];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for(int i = 0; i < 20; ++i) {
        seg[i].build(1, 1, n); // 初始化所有二进制位上的线段树 
    }
    for(int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        for(int j = 0; j < 20; ++j) {
            seg[j].update(1, 1, n, i, i, (x >> j) & 1);
        }
    }
    cin >> m;
    for(int i = 1; i <= m; ++i) {
        int opt, l, r;
        cin >> opt >> l >> r;
        if(opt == 1) {
            i64 ans = 0;
            for(int j = 0; j < 20; ++j) {
                ans += 1ll * (1 << j) * seg[j].query(1, 1, n, l, r);
            }
            cout << ans << '\n';
        } else {
            i64 x;
            cin >> x;
            for(int j = 0; j < 20; ++j) {
                seg[j].update(1, 1, n, l, r, (x >> j) & 1);
            }
        }
    }
    return 0;
}