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\)函数

posted @ 2020-10-23 11:28  When_C  阅读(104)  评论(0编辑  收藏  举报