线段树上维护状压(位运算)

洛谷P1558

分析:

颜色类型只有 \(30\) 种,可以利用二进制进行状压。
线段树维护一个二进制数表示区间的颜色为哪一种,将这个区间的颜色进行状压,每一种颜色对应二进制数的某一位。合并区间时将两个子节点的数按位或即可,题目区间修改为直接覆盖,统计答案时只需统计对应区间的数有多少个 \(1\) 即可。

代码:

折叠/打开
#include <cstdio>
#include <iostream>
#define maxn 4000005
using namespace std;

int l, r, k, p, T;
char check;
int n, q, a, b, c;
inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return x * f;
}

struct Tree {
    int l, r, ans, xr, lazy, len;
} t[maxn << 2];

inline void update(int node) { t[node].ans = t[node << 1].ans | t[node << 1 | 1].ans; }

inline void pushdown(int node) {
    if (t[node].lazy) {
        t[node << 1].lazy = t[node << 1 | 1].lazy = t[node].lazy;
        t[node << 1].ans = t[node << 1 | 1].ans = 1 << t[node].lazy;
        t[node].lazy = 0;
    }
}

inline void build(int l, int r, int node) {
    t[node].l = l, t[node].r = r, t[node].len = r - l + 1;
    if (l == r) {
        t[node].ans = 2;
        return;
    }
    //初始类型都为1
    int mid = l + r >> 1;
    build(l, mid, node << 1);
    build(mid + 1, r, node << 1 | 1);
    update(node);
}

inline void change(int l, int r, int k, int node) {
    if (l <= t[node].l && t[node].r <= r) {
        t[node].ans = (1 << k);
        t[node].lazy = k;
        return;
    }
    //因为是染色所以直接覆盖
    int mid = t[node].l + t[node].r >> 1;
    pushdown(node);
    if (l <= mid)
        change(l, r, k, node << 1);
    if (mid < r)
        change(l, r, k, node << 1 | 1);
    update(node);
}

inline int ask(int l, int r, int node) {
    if (l <= t[node].l && t[node].r <= r) {
        return t[node].ans;
    }
    pushdown(node);
    int ans = 0;
    int mid = t[node].l + t[node].r >> 1;
    if (l <= mid)
        ans |= ask(l, r, node << 1);
    if (mid < r)
        ans |= ask(l, r, node << 1 | 1);
    return ans;
}

int main() {
    n = read(), q = read();
    T = read();
    build(1, n, 1);
    while (T--) {
        int x, y, z;
        char op;
        cin >> op >> x >> y;
        if (x > y)
            swap(x, y);
        if (op == 'C') {
            z = read();
            change(x, y, z, 1);
        } else {
            int otto = ask(x, y, 1);
            int ans = 0;

            for (int i = 1; i <= q; i++)
                if (otto & (1 << i))
                    ans++;
            //处理出有多少个1即为不同的种类数
            printf("%d\n", ans);
        }
    }
    return 0;
}

LYOI #1194 League of Legends

题目大意:

\(n\)个棋盘,每个棋盘初始有一个类型为\(1\)的棋子,最多有\(28\)种棋子,对于棋盘有四种操作。

  1. \([l,r]\) 号棋盘中,若没有 \(p\) 类型的棋子,则放入一枚 \(p\) 类型的棋子。
  2. 表示在第 \(k\) 号棋盘中,若有 \(p\) 类型的棋子,则将其移除。
  3. 表示询问第 \([l,r]\) 号棋盘中,有几种类型的棋子出现了奇数次。
  4. 表示询问第 \([l,r]\) 号棋盘中,有几种类型的棋子在每一个棋盘中都出现过。

分析(重点在对应的代码讲解):

对于位运算的前置知识:

x&1 如果答案为1则x为奇数,如果答案为0则x为偶数
a&=(b^a)就是将a中所有为1的位改成0

线段树上二进制状压,思路和上题类似。
线段树每个节点维护两个值 \(ans,xr\)
\(ans\)用来维护棋子 \(p\) 在区间每一个棋盘内是否都出现过;\(xr\) 维护棋子出现次数的奇偶。

build建树:每个节点初始为类型为 \(1\)
update合并区间:将左右子区间的 \(ans\) 按位与起来,因为是每一个区间都要出现所以用按位与;将左右子区间的 \(xr\) 异或,偶数为 \(0\) 奇数为 \(1\) ,可保留出现的奇数次数。

折叠/打开
inline void update(int node) {
    t[node].xr = t[node << 1].xr ^ t[node << 1 | 1].xr;
    t[node].ans = t[node << 1].ans & t[node << 1 | 1].ans;
}

pushdown下传标记:子区间懒标记异或上当前节点懒标记向下传递;判断区间长度是否为奇数,如果区间为奇数,则满足询问奇数操作,就把子区间 \(xr\) 异或上懒标记,下传到子节点。如果为偶数且被某种棋子覆盖,对答案没有影响,所以将偶数区间内打上标记的棋子删除。

折叠/打开
inline void pushdown(int node) {
    t[node << 1].lazy |= t[node].lazy;
    t[node << 1 | 1].lazy |= t[node].lazy;

    if (t[node << 1].len & 1)
        t[node << 1].xr |= t[node].lazy;
    else
        t[node << 1].xr &= (t[node].lazy ^ t[node << 1].xr);
    if (t[node << 1 | 1].len & 1)
        t[node << 1 | 1].xr |= t[node].lazy;
    else
        t[node << 1 | 1].xr &= (t[node].lazy ^ t[node << 1 | 1].xr);
    t[node << 1].ans |= t[node].lazy;
    t[node << 1 | 1].ans |= t[node].lazy;
    t[node].lazy = 0;
}

修改change:\([l,r]\) 中没有 \(p\) 类的棋子就放入一枚。所以在所以对于区间 \(ans\) 要或上第\(p\)位的\(1\),代表加入了 \(p\) 类型的棋子。
删除change_p:\(k\)号棋盘中若有 \(p\) 类棋子就将其删除。所以对于区间的\(ans\)\(xr\)都要与上\(p\)位的\(0\),表示将\(p\)类棋子删除。
询问ask_1:询问区间内有多少种棋子出现奇数次。如果两个子区间内\(p\)这个棋子都出现了奇数次(则该位为\(1\))或偶数次(则该位为\(0\)),那么总的区间\(q\)这个棋子总共就出现了偶数次。所以为了只统计奇数的棋子,需要在统计左或右区间时将答案进行异或。
询问ask_2:询问区间中有几种棋子在每个棋盘都出现过。与第一种询问同理,统计子区间答案时要将答案按位与。

全代码:

#include <cstdio>

#define lowbit(a) (a & (-a))
#define maxn 4000005

int l, r, k, p, check;
int n, q, a, b, c;

inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return x * f;
}

struct Tree {
    int l, r, ans, xr, lazy, len;
} t[maxn << 2];

inline void update(int node) {
    t[node].xr = t[node << 1].xr ^ t[node << 1 | 1].xr;
    t[node].ans = t[node << 1].ans & t[node << 1 | 1].ans;
}

inline void pushdown(int node) {
    t[node << 1].lazy |= t[node].lazy;
    t[node << 1 | 1].lazy |= t[node].lazy;

    if (t[node << 1].len & 1)
        t[node << 1].xr |= t[node].lazy;
    else
        t[node << 1].xr &= (t[node].lazy ^ t[node << 1].xr);
    if (t[node << 1 | 1].len & 1)
        t[node << 1 | 1].xr |= t[node].lazy;
    else
        t[node << 1 | 1].xr &= (t[node].lazy ^ t[node << 1 | 1].xr);
    t[node << 1].ans |= t[node].lazy;
    t[node << 1 | 1].ans |= t[node].lazy;
    t[node].lazy = 0;
}



inline void build(int l, int r, int node) {
    t[node].l = l, t[node].r = r, t[node].len = r - l + 1;
    if (l == r) {
        t[node].xr = 2;
        t[node].ans = 2;
        return;
    }
    int mid = l + r >> 1;
    build(l, mid, node << 1);
    build(mid + 1, r, node << 1 | 1);
    update(node);
}


inline void change(int l, int r, int k, int node){
    if (l <= t[node].l && t[node].r <= r) {
        t[node].ans |= k;
        if (t[node].len & 1)
            t[node].xr |= k;
        else
            t[node].xr &= (t[node].xr ^ k);
        t[node].lazy |= k;
        return;
    }
    int mid = t[node].l + t[node].r >> 1;
    if (t[node].lazy)
        pushdown(node);
    if (l <= mid)
        change(l, r, k, node << 1);
    if (mid < r)
        change(l, r, k, node << 1 | 1);
    update(node);
}

inline void change_p(int p, int k, int node) {
    if (t[node].l == t[node].r) {
        t[node].ans &= k;
        t[node].xr &= k;
        return;
    }
    int mid = t[node].l + t[node].r >> 1;
    if (t[node].lazy)
        pushdown(node);
    if (p <= mid)
        change_p(p, k, node << 1);
    else
        change_p(p, k, node << 1 | 1);
    update(node);
}

inline int ask_1(int l, int r, int node) {
    if (l <= t[node].l && t[node].r <= r) {
        return t[node].xr;
    }
    if (t[node].lazy)
        pushdown(node);
    int ans = 0;
    int mid = t[node].l + t[node].r >> 1;
    if (l <= mid)
        ans = ask_1(l, r, node << 1);
    if (mid < r)
        ans ^= ask_1(l, r, node << 1 | 1);
    return ans;
}

inline int ask_2(int l, int r, int node) {
    if (l <= t[node].l && t[node].r <= r) {
        return t[node].ans;
    }
    if (t[node].lazy)
        pushdown(node);
    int ans = 1073741823;
    int mid = t[node].l + t[node].r >> 1;
    if (l <= mid)
        ans = ask_2(l, r, node << 1);
    if (mid < r)
        ans &= ask_2(l, r, node << 1 | 1);
    return ans;
}

int main() {
    freopen("lol.in", "r", stdin);
    freopen("lol.out", "w", stdout);
    n = read(), q = read();
    build(1, n, 1);
    while (q--) {
        check = read();
        if (check == 1) {
            l = read(), r = read(), p = read();
            change(l, r, (1 << p), 1);
        } else if (check == 2) {
            k = read(), p = read();
            int kkksb = 1073741823;
            change_p(k, (kkksb ^ (1 << p)), 1);
        } else if (check == 3) {
            l = read(), r = read();
            int num = ask_1(l, r, 1), cnt = 0;
            while (num) {//计算这个数有几个1
                num -= lowbit(num);
                ++cnt;
            }
            printf("%d\n", cnt);
        } else {
            l = read(), r = read();
            int num = ask_2(l, r, 1), cnt = 0;
            while (num) {
                num -= lowbit(num);
                ++cnt;
            }
            printf("%d\n", cnt);
        }
    }
    return 0;
}

posted @ 2022-09-08 20:17  DAIANZE  阅读(36)  评论(0编辑  收藏  举报