Luogu P2572 [SCOI2010]序列操作(線段樹)題解
阿新 • • 發佈:2020-10-23
細節狂魔題
題意:維護一個\(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\)函式