1. 程式人生 > >第十四屆中北大學ACM程序設計競賽 J.ZBT的遊戲

第十四屆中北大學ACM程序設計競賽 J.ZBT的遊戲

並集 然而 思想 pda color 引號 遞歸 接下來 pushd

問題描述

第14屆中北大學程序設計競賽來了,集訓隊新買了一大堆氣球,氣球一共有K種顏色(1<=K<=256),氣球的顏色從1-K編號。

ZBT童心未泯,他發明了一種擺放氣球的遊戲,規則如下。

一排有N個桌子,每張桌子上只有一個氣球插孔,即每張桌子最多只能放一個氣球。編號分別為1-N(1<=N<=100000),每張桌子一開始是空的。現在對這張桌子要進行M次操作(1<=M<=100000),操作的種類一共有2種。

操作1:
操作指令格式: CHANGE L R C

操作含義:在編號為L至編號為R的桌子分別放置顏色為C的氣球(如果這些桌子上曾經有氣球,則取下原來的氣球。因為每張桌子上只能放置一個氣球)

操作2:
操作指令格式: QUERY L R

操作含義:輸出編號為L到編號為R的桌子上的氣球顏色種類數
現在他要求你寫程序來完成他的操作,程序的輸入輸出見輸入、輸出描述

輸入描述

第1行是三個整數N和M以及K,用空格隔開,分別代表桌子的個數、要進行操作指令的個數、以及氣球的顏色總數。

接下來M行,每行一個操作指令,格式如上,保證指令中的1<=L<=R<=N, 1<=C<=K

輸出描述

如果操作指令中有查詢操作(操作2),那麽對於每個操作2輸出一行,該行中只有一個整數即為該查詢操作的答案。

如果全部操作指令中都沒有查詢操作(操作2),那麽請輸出” This is a boring game!”(不含引號)

樣例輸入

10 20 5
QUERY 6 8
CHANGE 5 8 5
CHANGE 2 3 5
CHANGE 9 10 1
QUERY 9 9
QUERY 8 10
CHANGE 2 4 4
CHANGE 9 9 2
QUERY 2 2
CHANGE 8 10 1
CHANGE 6 9 3
CHANGE 10 10 2
QUERY 3 5
QUERY 6 8
QUERY 2 5
QUERY 5 5
QUERY 3 9
QUERY 4 10
CHANGE 5 8 1
QUERY 7 8

樣例輸出

0
1
2
1
2
1
2
1
3
4
1

題意:維護顏色序列,支持以下操作

  • 區間覆蓋(顏色修改)
  • 區間查詢顏色種類

前置技能:

  • 線段樹基本操作(區間覆蓋、區間查詢)
  • 狀態壓縮思想

顯然,如果是單點修改的話,等同於洛谷P1903 [國家集訓隊]數顏色 。有離線做法:CDQ+樹狀數組/帶修莫隊和 在線做法:樹套樹 等多種優雅解法(然而本人都不會)。

但是,因為本題有區間覆蓋的操作,導致上述做法失效或轉移復雜度過高。

所以該怎麽做呢?

本題的操作都是區間操作,可以想到用線段樹。觀察到顏色種類只有256種,因此可以在每個線段樹的結點上存儲一個集合,表示這個結點代表的區間裏出現顏色的種類。

維護時,區間覆蓋還是打Lazytag。當某個區間被完全覆蓋需要修改時,把集合中的元素清空,只存入當前修改的一種顏色。

每次遞歸並下傳標記後,當前結點的顏色集合等於左右子節點的顏色集合的並集。

具體維護集合,可以用bitset或者用4個longlong類型變量(相當於手寫bitset)。

這裏每個結點開一個bitset<300> dat;

區間被完全覆蓋,先把這個結點的顏色集合清空,即node[p].dat=0;然後集合裏只有覆蓋的這種顏色color,即node[p].dat[color]=1。

每次完成對左右兒子的修改後上傳操作,當前結點的顏色集合等於左右子節點的顏色集合的並集,即node[p].dat=node[p<<1].dat|node[p<<1|1].dat 。


Code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, k;
bitset<300> ans;//統計答案用 
struct SegmentTree {
    int l, r, tag;
    bitset<300> dat;//相當於每個結點存儲一個顏色集合 
#define l(p) (node[p].l)
#define r(p) (node[p].r)
#define tag(p) (node[p].tag)
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l(p)+r(p))>>1)
} node[N << 2];
void build(int p, int l, int r) {
    l(p) = l;
    r(p) = r;
    if(l == r) return;//初始沒有顏色 都是0
    build(ls(p), l, mid);
    build(rs(p), mid + 1, r);
}
void update(int p, int v) {//結點p被顏色v完全覆蓋
    tag(p) = v;
    node[p].dat = 0;//清空集合
    node[p].dat[v] = 1;//集合裏只有顏色v
}
void pushdown(int p) {//下傳結點p標記
    if(tag(p)) {
        update(ls(p), tag(p));//更新左右結點
        update(rs(p), tag(p));
        tag(p) = 0;//清空標記
    }
}
void change(int p, int L, int R, int v) {
    if(l(p) > R || r(p) < L) return;//修改區間與該節點表示區間沒有交集
    if(L <= l(p) && r(p) <= R) return update(p, v);//該節點被完全覆蓋
    pushdown(p);//下傳標記
    change(ls(p), L, R, v);//修改左右兒子結點
    change(rs(p), L, R, v);
    node[p].dat = node[ls(p)].dat | node[rs(p)].dat;//當前顏色集合等於左右兒子的集合的並集
}
void query(int p, int L, int R) {//查詢時同理
    if(l(p) > R || r(p) < L) return;
    if(L <= l(p) && r(p) <= R) {
        ans = ans | node[p].dat;//這裏ans是全局變量
        return;
    }
    pushdown(p);
    query(ls(p), L, R);
    query(rs(p), L, R);
    node[p].dat = node[ls(p)].dat | node[rs(p)].dat;
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    build(1, 1, n);
    char op[10];
    int l, r, c;
    bool flag = false;
    while(m--) {
        scanf("%s%d%d", op, &l, &r);
        if(op[0] == 'C') {
            scanf("%d", &c);
            change(1, l, r, c);
        } else if(op[0] == 'Q') {
            flag = true;
            ans = 0;//清空ans
            query(1, l, r);
            int res = ans.count();//ans.count()返回ans中有幾位是1
            printf("%d\n", res);
        }
    }
    if(!flag) puts("This is a boring game!");
    return 0;
}

第十四屆中北大學ACM程序設計競賽 J.ZBT的遊戲