P4314 CPU 監控
分析
前置知識:線段樹
我們先來看看操作。
操作
給出 T 個數,E 個操作(\(T,E \leq 10^5\))
-
Q X Y
:詢問從 X 到 Y 的當前最大值 -
A X Y
:詢問從 X 到 Y 的歷史最大值(出現過的最大數) -
P X Y Z
:將 X 到 Y 這段區間加 Z -
C X Y Z
:將 X 到 Y 這段區間賦值為 Z
操作實現
這道題可以說是非常好的一道題目了,非常考查選手對懶標記的理解。
(下邊這段話來自He_Ren大佬,但我會說一些自己的理解,希望能對你的理解有幫助)
其實這道題完全可以作為線段樹的模板題,考察了 lazy tag
與樹上節點的更新順序的關係
如何理解?
懶標記
實際上可以看作是對於該節點表示的區間的操作序列,這也是線段樹的精髓所在
push_down
操作就是把父節點的操作序列接在兒子節點的序列之後。其實很好理解,原本壓在子區間的操作,是按順序一個一個來的。
對一個點進行 push_down
操作後,該點的操作序列即被清空,因為在遞迴完子樹後,該點答案將被更新。以下文章中的 “ 操作序列 ”,都指的是該點上一次進行 push_down
後(即上一次清空後)的操作序列。即父節點pushdown作用在該節點的操作,但還未再次掃到該節點,所以操作序列並未在向下傳遞,則操作系列未被清空。
如何處理操作序列?
首先,相鄰的同種操作可被合併
於是,對於一個點,操作序列分為以下幾種( "+" 號表示操作的先後順序,漢字敘述可能影響您的閱讀體驗):
- 加操作
- 加操作 + 賦值操作
- 加操作+賦值操作+加操作
- 加操作+賦值操作+加操作+賦值操作
- ⋯
這個操作序列顯然很難儲存在每一個點上
我們發現,對於一個點,首次賦值操作之後的任何修改都可以看作賦值操作,因為這樣區間的所有數都相同了,若實現區間加,則直接看為對區間賦值的改變即可。
於是操作序列化簡為(重點):
加操作 + 賦值操作
現在,恭喜您解決了兩個個很大的問題:操作序列如何儲存 & 同一點上操作的先後順序問題
於是,只用儲存加和賦值兩種 lazy tag
錯誤分析
我在這裡寫一下,自己的錯誤歷程,主要為了警醒自己不要犯重複錯誤,也希望能提供一些可能出現的錯誤的提醒。
首先是,我剛開始寫的時候,我覺得很簡單,我直接維護兩個懶標記cov
和add
,來維護區間覆蓋與區間加即可。
只需接著維護最大值與歷史最大值即可。維護歷史最大值的時候,就每次更新最大值的時候,順便更新一下歷史最大值即可。
但這樣的思路,大錯特錯。若向我這麼寫,則區間覆蓋和區間加是有一定的先後順序的,我在進行區間覆蓋時,會直接將add
清零,則這會出現問題,因為原來的add
沒被加上,這會影響區間最大值。
因此,在這題中,我們不能考慮優先順序的問題,要將所有操作保留下來,這樣才能求出歷史最大值
其餘的,我會在程式碼中進行儘量詳細的解釋。
其中重點是,pushdown操作,相信看完後,一定會對你有所用處
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Node
{
int l,r,mx,his;
bool iscov;//是有區間覆蓋
int add,cov;//從上次清空後,區間加,區間覆蓋累積的值
int mxadd,mxcov;//從上次清空後,區間加,區間覆蓋所積累過程中的最大值
//所以其實這裡用了四個懶標記,下邊兩個的作用,我會盡量在下邊說清楚
}tr[N<<2];
int n,m;
void pushup(int u)//pushup非常好說,就直接用子節點更新父節點即可
{
tr[u].mx = max(tr[u<<1].mx,tr[u<<1|1].mx);
tr[u].his = max(tr[u<<1].his,tr[u<<1|1].his);
}
void build(int u,int l,int r)
{
if(l==r)
{
int x;
scanf("%d",&x);
tr[u] = {l,r,x,x};
return ;
}
tr[u] = {l,r};
int mid = l + r >> 1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
/**
* 在這裡,我還是要反覆說,將複雜的懶標記操作直接封裝成函式。
* 這樣會大大減少我們犯錯誤的可能。
* 在下傳懶標記的時候,我們要考慮對所有維護的變數的影響。
* 記住,向下傳遞的操作是,用父區間改變子區間
*/
void changeadd(Node &u,int k,int mxk)//對區間加操作的實現,k為這次區間加的值,而mxk則為父區間歷史最大區間加的值,
{
if(u.iscov)//判斷該區間是否已經有賦值操作了。
{
//若有,則接下來直接將區間加操作,改為對區間賦值操作的改變
u.mxcov = max(u.mxcov,u.cov+mxk);//更新子區間歷史最大賦值
u.his = max(u.his,u.mx+mxk);//更新子區間歷史最大值
u.mx += k;//加上本次要加的值
u.cov += k;//加上本次要加的值
}
else
{
//若沒有就繼續累加區間加操作。
u.mxadd = max(u.mxadd,u.add+mxk);//更新子區間歷史最大加和值
u.his = max(u.his,u.mx+mxk);//更新子區間歷史最大值
u.mx += k;//加上本次要加的值
u.add += k;//加上本次要加的值
}
}
void changecov(Node &u,int k,int mxk)//對區間加操作的實現,k為這次區間賦值的值,而mxk則為父區間歷史最大賦值的值,
{
if(u.iscov)
{
//若已經被賦值
u.mxcov = max(u.mxcov,mxk);//則更新子區間的歷史最大賦值
u.his = max(u.his,mxk);//更新子區間的歷史最大值
}
else
{
u.iscov = 1;//標記已經覆蓋
u.mxcov = mxk;//第一次覆蓋,則對於子區間來說,歷史最大覆蓋就是mxk
u.his = max(u.his,mxk);//更新子區間的歷史最大值
}
u.mx = u.cov = k;//更新一下mx和cov
}
void pushdown(int u)
{
auto &root = tr[u],&left = tr[u<<1],&right = tr[u<<1|1];//在我們合併後的操作序列中,先加後賦值
changeadd(left,root.add,root.mxadd);
changeadd(right,root.add,root.mxadd);
root.mxadd = root.add = 0;//操作後,清空兩個標記
if(root.iscov)//是否有覆蓋
{
changecov(left,root.cov,root.mxcov);
changecov(right,root.cov,root.mxcov);
root.iscov = 0;//更新後,標記無覆蓋了。
//這裡沒有清空關於區間覆蓋的兩個值,因為沒必要,我們都是直接改變區間值的。
}
}
void modifyadd(int u,int l,int r,int k)
{
if(l<=tr[u].l&&tr[u].r<=r)
{
changeadd(tr[u],k,k);//改變該區間的和,可以直接當做一個歷史加和最大值為k,且本次要加的也為k的父區間
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l<=mid) modifyadd(u<<1,l,r,k);
if(r>mid) modifyadd(u<<1|1,l,r,k);
pushup(u);
}
void modifycov(int u,int l,int r,int k)
{
if(l<=tr[u].l&&tr[u].r<=r)
{
changecov(tr[u],k,k);//與上同理
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l<=mid) modifycov(u<<1,l,r,k);
if(r>mid) modifycov(u<<1|1,l,r,k);
pushup(u);
}
int querymx(int u,int l,int r)
{
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].mx;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int res = -INT_MAX;
if(l<=mid) res = max(res,querymx(u<<1,l,r));
if(r>mid) res = max(res,querymx(u<<1|1,l,r));
return res;
}
int queryhis(int u,int l,int r)
{
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].his;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int res = -INT_MAX;
if(l<=mid) res = max(res,queryhis(u<<1,l,r));
if(r>mid) res = max(res,queryhis(u<<1|1,l,r));
return res;
}
int main()
{
scanf("%d",&n);
build(1,1,n);
scanf("%d",&m);
while(m--)
{
char op[2];
int x,y,z;
scanf("%s%d%d",op,&x,&y);
if(*op=='Q') printf("%d\n",querymx(1,x,y));
else if(*op=='A') printf("%d\n",queryhis(1,x,y));
else if(*op=='P')
{
scanf("%d",&z);
modifyadd(1,x,y,z);
}
else
{
scanf("%d",&z);
modifycov(1,x,y,z);
}
}
return 0;
}