Luogu P4314 CPU監控
思路
對於這道題,為了好想,我們先考慮一下如果不考慮歷史最大值應該怎麼做。這樣的話,我們需要做區間加和區間推平的操作。那麼,做過線段樹2的都知道,我們應該是需要兩個tag來維護,分別是區
間累加和區間推平。那麼對於這個題,我們怎麼在pushdown的時候相容這兩個tag呢?
很顯然,無論你先做區間加還是先做區間推平都是不行的。假設我們有這樣一個操作序列:+1 +2 +4 推平2 +5 -3 。很顯然按照給出的操作序列,得到的結果是4。如果我們先做區間加,再做區間推
平,那麼得到的結果是2,這很明顯不對。那如果我們先做區間推平,再做區間加,那麼得到的結果是11,這很明顯也不對……那我們應該怎麼做呢?
很明顯,影響整個過程的是區間推平操作。區間推平之前的區間加操作其實是不受影響的,問題就是在區間推平之後的區間加操作。在C++語法中,我們知道b += c和b = b + c是等價的。根據這個,
我們可以考慮把在第一次區間推平之後的所有區間加操作轉化為區間推平操作。這個具體實現一會在程式碼裡寫。
那如果加上維護歷史最大值的操作,應該怎麼做呢?和維護當前區間最大值類似,我們也是要維護區間累加和區間推平,然後在更新的時候注意要用未更新前的當前最大值更新,不要擾亂時間線。但是,
單單和當前區間最大值一樣維護是不行的。比如下面這種情況:
對於這樣的一個操作序列,如果我們僅僅是把歷史最大值和當前最大值一起做更新(即只考慮把tag全部放下之後的情況)就會導致上面這種情況。很明顯,這張圖的歷史最大值是在藍色點所在處。
因為tag中是集成了所有詢問和更新之前的操作,所以歷史最大值很可能是出現在中間過程,而不是最後結果。那該怎麼辦呢?
有人可能會直接想到每次更新之後直接下放標記。但這樣做會讓你線段樹優秀的\(O(n log n)\) 時間複雜度暴漲至\(O(n^2logn)\) 。然後你就過不了這道題了。
那該如何做呢?我們需要把維護歷史最大的兩個標記的定義更改一下。我們讓維護歷史最大的兩個標記分別定義為歷史最大累加和歷史最大推平,然後在更新維護當前的兩個tag的時候一起更新,只不
過把加或者賦值改為取max了。維護的時候先下放已有的加法標記,再推平(這裡因為第一次推平之後的所有加法都轉化成了賦值,所以不會出現加法被覆蓋的情況)。
Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #define MAXN 100005 #define INF 0x7fffffff typedef long long ll; int T, a[MAXN], E; struct node{ int nmax, hmax;//當前區間最大值、歷史區間最大值 int ntag1, htag1;//當前累加tag、歷史最大累加tag int ntag2, htag2;//當前最大賦值tag、歷史最大賦值tag int flag;//0表示沒有被推平過,1表示已經被推平過 } tree[MAXN << 2]; inline int lson(int k) { return k << 1; } inline int rson(int k) { return k << 1 | 1; } inline void push_up(int k){ tree[k].nmax = std::max(tree[lson(k)].nmax, tree[rson(k)].nmax); tree[k].hmax = std::max(tree[lson(k)].hmax, tree[rson(k)].hmax); return;//push_up沒有什麼特殊的,基本操作 } void build(int k,int l,int r){ tree[k].ntag1 = 0, tree[k].htag1 = 0; tree[k].ntag2 = -INF, tree[k].htag2 = -INF; tree[k].flag = 0;//這裡初始化欽定-INF為沒有推平 if(l==r){ tree[k].nmax = a[l], tree[k].hmax = a[l]; return; } int mid = (l + r) >> 1; build(lson(k), l, mid); build(rson(k), mid + 1, r); push_up(k); }//建樹操作 inline void change(int k,int nv,int hv){//賦值函式 tree[k].flag = 1;//不要忘了把賦值標記設為1 tree[k].htag2 = std::max(tree[k].htag2, hv);//這裡是取max tree[k].ntag2 = nv;//維護賦值的兩個tag tree[k].hmax = std::max(tree[k].hmax, hv); tree[k].nmax = nv;//更新賦值後的nmax和hmax return; } inline void add(int k,int nv,int hv){ if(tree[k].flag == 1){ change(k, tree[k].nmax + nv, tree[k].nmax + hv); return;//如果當前區間已經被推平過,那麼把累加轉化為推平 } tree[k].htag1 = std::max(tree[k].htag1, tree[k].ntag1 + hv); tree[k].ntag1 += nv;//這裡維護歷史值的時候不要忘了是用當前值更新,否則會擾亂時間線 tree[k].hmax = std::max(tree[k].hmax, tree[k].nmax + hv); tree[k].nmax += nv;//同上 return; } inline void push_down(int k){ add(lson(k), tree[k].ntag1, tree[k].htag1); add(rson(k), tree[k].ntag1, tree[k].htag1);//先下放已有的累加標記 if(tree[k].flag == 1){ change(lson(k), tree[k].ntag2, tree[k].htag2); change(rson(k), tree[k].ntag2, tree[k].htag2); }//若被推平過,下放推平標記,否則會漏掉一些操作(因為一些累加被轉化為了推平) tree[k].flag = 0;//標記下放完了之後將標記復原 tree[k].ntag1 = 0, tree[k].ntag2 = -INF; tree[k].htag1 = 0, tree[k].htag2 = -INF; return; } void update_add(int k,int l,int r,int cl,int cr,int v){ if(cl<=l&&r<=cr){ add(k, v, v);//這裡給出的歷史和當前是一樣的,因為是更新操作 return; } push_down(k); int mid = (l + r) >> 1; if(cl<=mid) update_add(lson(k), l, mid, cl, cr, v); if(cr>mid) update_add(rson(k), mid + 1, r, cl, cr, v); push_up(k); }//update沒什麼可說的 void update_change(int k,int l,int r,int cl,int cr,int v){ if(cl<=l&&r<=cr){ change(k, v, v); return; } push_down(k); int mid = (l + r) >> 1; if(cl<=mid) update_change(lson(k), l, mid, cl, cr, v); if(cr>mid) update_change(rson(k), mid + 1, r, cl, cr, v); push_up(k); }//同上 int query_now(int k,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr) return tree[k].nmax; push_down(k); int mid = (l + r) >> 1; int res = -INF;//取max不要忘了賦值為-INF if(ql<=mid) res = std::max(res, query_now(lson(k), l, mid, ql, qr)); if(qr>mid) res = std::max(res, query_now(rson(k), mid + 1, r, ql, qr)); return res; } int query_history(int k,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr) return tree[k].hmax; push_down(k); int mid = (l + r) >> 1; int res = -INF; if(ql<=mid) res = std::max(res, query_history(lson(k), l, mid, ql, qr)); if(qr>mid) res = std::max(res, query_history(rson(k), mid + 1, r, ql, qr)); return res; }//同上 int main(){ scanf("%d",&T); for (int i = 1; i <= T;++i) scanf("%d", &a[i]); build(1, 1, T);//千萬不要忘了建樹啊 scanf("%d", &E); for (int i = 1; i <= E;++i){ char opt[20]; int x = 0, y = 0, z = 0; scanf("%s", opt); scanf("%d%d", &x, &y); if(opt[0]=='Q') printf("%d\n", query_now(1, 1, T, x, y)); if(opt[0]=='A') printf("%d\n", query_history(1, 1, T, x, y)); if(opt[0]=='P'){ scanf("%d", &z); update_add(1, 1, T, x, y, z); } if(opt[0]=='C'){ scanf("%d", &z); update_change(1, 1, T, x, y, z); } }//主函式沒有什麼特別的 return 0;