1. 程式人生 > 實用技巧 >Luogu P2572 [SCOI2010]序列操作(線段樹)題解

Luogu P2572 [SCOI2010]序列操作(線段樹)題解

細節狂魔題

題意:維護一個\(01\)序列,支援區間修改(全部變\(0\)\(1\)),區間取反,區間求和,區間求最長1序列

做法

顯然應該用線段樹維護。

線段樹需要維護:每段區間內\(0\)的數量,\(1\)的數量,最長\(1\)序列長度,最長\(0\)序列長度,從左端點開始的0/1序列長度,從右端點開始的\(0/1\)序列長度。很少吧

注意!在標記下傳時會有先後,賦值標記會覆蓋翻轉標記,而翻轉標記會影響賦值標記

為什麼我覺得網上的題解都寫的好複雜,其實區間修改與取反可以寫在一起,求序列與求和也可以很方便地寫出來。

程式碼

#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

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 * 10 + c - '0'; c = getchar();}
    return x * f;
}

const int maxn = 1e5 + 10;
int n,m;

struct Seg_Tree{
    #define lc(x) x << 1
    #define rc(x) x << 1 | 1
    struct node{
        int c,c0,sum,lm,lm0,rm,rm0,tag,re;
    }t[maxn << 2];
    // c->num of continued 1; c0->num of continued 0;
    
    void update(int l, int r, int p){
        int mid = (l + r) >> 1;
        t[p].sum = t[rc(p)].sum + t[lc(p)].sum;
        t[p].c = max( max(t[lc(p)].c, t[rc(p)].c), t[lc(p)].rm + t[rc(p)].lm );
        t[p].c0 = max( max(t[lc(p)].c0, t[rc(p)].c0), t[lc(p)].rm0 + t[rc(p)].lm0 );
        
        if(t[lc(p)].lm == mid - l + 1) t[p].lm = mid - l + 1 + t[rc(p)].lm;
        else t[p].lm = t[lc(p)].lm;
        if(t[rc(p)].rm == r - mid) t[p].rm = r - mid + t[lc(p)].rm;
        else t[p].rm = t[rc(p)].rm;
        
        if(t[lc(p)].lm0 == mid - l + 1) t[p].lm0 = mid - l + 1 + t[rc(p)].lm0;
        else t[p].lm0 = t[lc(p)].lm0;
        if(t[rc(p)].rm0 == r - mid) t[p].rm0 = r - mid + t[lc(p)].rm0;
        else t[p].rm0 = t[rc(p)].rm0;
    }
    
    void build(int l, int r, int p){
        t[p].tag = 0;
        if(l == r){
            t[p].lm = t[p].rm = t[p].c = t[p].sum = read();
            t[p].lm0 = t[p].rm0 = t[p].c0 = t[p].c ^ 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(l, mid, lc(p)); build(mid + 1, r, rc(p));
        update(l, r, p);
    }
    
    void f(int l, int r, int p, int typ){ //1 -> to 0; 2 -> to 1; 3 -> to ~
        if(typ == 1){
            t[p].sum = t[p].c = t[p].lm = t[p].rm = 0;
            t[p].c0 = t[p].lm0 = t[p].rm0 = r - l + 1;
            t[p].tag = 1; t[p].re = 0;
        }
        if(typ == 2){
            t[p].sum = t[p].c = t[p].lm = t[p].rm = r - l + 1;
            t[p].c0 = t[p].lm0 = t[p].rm0 = 0;
            t[p].tag = 2; t[p].re = 0;
        }
        if(typ == 3){
            t[p].sum = r - l + 1 - t[p].sum;
            swap(t[p].c0, t[p].c); swap(t[p].lm, t[p].lm0); swap(t[p].rm, t[p].rm0);
            if(t[p].tag == 1) t[p].tag = 2;
            else if(t[p].tag == 2) t[p].tag = 1;
            else t[p].re ^= 1;
        }
    }
    
    void downdate(int l, int r, int p){
        if(t[p].tag){
            int mid = (l + r) >> 1;
            f(l, mid, lc(p), t[p].tag);
            f(mid + 1, r, rc(p), t[p].tag);
            t[p].tag = 0; t[p].re = 0;
        }
        if(t[p].re){
            int mid = (l + r) >> 1;
            f(l, mid, lc(p), 3);
            f(mid + 1, r, rc(p), 3);
            t[p].re = 0;
        }
    }
    
    void change(int L, int R, int l, int r, int p, int typ){
        if(L <= l && R >= r){
            f(l, r, p, typ);
            return;
        }
        downdate(l, r, p);
        int mid = (l + r) >> 1;
        if(mid >= L) change(L, R, l, mid, lc(p), typ);
        if(mid < R) change(L, R, mid + 1, r, rc(p), typ);
        update(l, r, p);
    }
    
    int query(int L, int R, int l, int r, int p){ //get the num of continued 1
        if(L <= l && R >= r) return t[p].c;
        downdate(l, r, p);
        int mid = (l + r) >> 1; int ans = 0;
        if(mid >= L) ans = query(L, R, l, mid, lc(p));
        if(mid < R) ans = max(ans, query(L, R, mid + 1, r, rc(p)));
        if(mid >= L && mid < R) ans = max(ans, min(t[lc(p)].rm, mid - L + 1) + min(t[rc(p)].lm, R - mid));
        return ans;
    }
    
    int get_sum(int L, int R, int l, int r, int p){
        if(L <= l && R >= r) return t[p].sum;
        downdate(l, r, p);
        int mid = (l + r) >> 1; int ans = 0;
        if(mid >= L) ans += get_sum(L, R, l, mid, lc(p));
        if(mid < R) ans += get_sum(L, R, mid + 1, r, rc(p));
        return ans;
    }
}tree;

int main(){
    n = read(), m = read();
    tree.build(1, n, 1);
    for(int i = 1; i <= m; ++ i){
        int opt = read(), x = read(), y = read();
        x += 1, y += 1;
        if(opt == 0) tree.change(x, y, 1, n, 1, 1);
        if(opt == 1) tree.change(x, y, 1, n, 1, 2);
        if(opt == 2) tree.change(x, y, 1, n, 1, 3);
        if(opt == 3) printf("%d\n", tree.get_sum(x, y, 1, n, 1));
        if(opt == 4) printf("%d\n", tree.query(x, y, 1, n, 1));
    }
    return 0;
}

真是一點也不難寫呢

總結

這類涉及帶修區間求子序列的題基本上都是這種思路,考慮三種情況:答案序列在左邊,答案序列在右邊,答案序列橫跨\(mid\),然後就可以很方(nan)便(tiao)地用線段樹維護了。

難想的主要是:\(update\)函式,\(downdate\)函式