1. 程式人生 > 其它 >P4314 CPU 監控

P4314 CPU 監控

P4314 CPU 監控

分析

前置知識:線段樹

我們先來看看操作。

操作

給出 T 個數,E 個操作(\(T,E \leq 10^5\)

  • Q X Y:詢問從 XY當前最大值
  • A X Y:詢問從 XY歷史最大值(出現過的最大數)
  • P X Y Z:將 XY 這段區間加 Z
  • C X Y Z:將 XY 這段區間賦值為 Z

操作實現

這道題可以說是非常好的一道題目了,非常考查選手對懶標記的理解。

(下邊這段話來自He_Ren大佬,但我會說一些自己的理解,希望能對你的理解有幫助)

其實這道題完全可以作為線段樹的模板題,考察了 lazy tag 與樹上節點的更新順序的關係

如何理解?

懶標記 實際上可以看作是對於該節點表示的區間的操作序列,這也是線段樹的精髓所在

push_down 操作就是把父節點的操作序列接在兒子節點的序列之後。其實很好理解,原本壓在子區間的操作,是按順序一個一個來的。

對一個點進行 push_down 操作後,該點的操作序列即被清空,因為在遞迴完子樹後,該點答案將被更新。以下文章中的 “ 操作序列 ”,都指的是該點上一次進行 push_down 後(即上一次清空後)的操作序列。即父節點pushdown作用在該節點的操作,但還未再次掃到該節點,所以操作序列並未在向下傳遞,則操作系列未被清空。

如何處理操作序列?

首先,相鄰的同種操作可被合併

。這是非常關鍵的。

於是,對於一個點,操作序列分為以下幾種( "+" 號表示操作的先後順序,漢字敘述可能影響您的閱讀體驗):

  1. 加操作
  2. 加操作 + 賦值操作
  3. 加操作+賦值操作+加操作
  4. 加操作+賦值操作+加操作+賦值操作

這個操作序列顯然很難儲存在每一個點上

我們發現,對於一個點,首次賦值操作之後的任何修改都可以看作賦值操作,因為這樣區間的所有數都相同了,若實現區間加,則直接看為對區間賦值的改變即可。

於是操作序列化簡為(重點):

加操作 + 賦值操作

現在,恭喜您解決了兩個個很大的問題:操作序列如何儲存 & 同一點上操作的先後順序問題

於是,只用儲存加和賦值兩種 lazy tag

,便可記錄該節點的操作序列

錯誤分析

我在這裡寫一下,自己的錯誤歷程,主要為了警醒自己不要犯重複錯誤,也希望能提供一些可能出現的錯誤的提醒。

首先是,我剛開始寫的時候,我覺得很簡單,我直接維護兩個懶標記covadd,來維護區間覆蓋區間加即可。

只需接著維護最大值與歷史最大值即可。維護歷史最大值的時候,就每次更新最大值的時候,順便更新一下歷史最大值即可。

但這樣的思路,大錯特錯。若向我這麼寫,則區間覆蓋區間加是有一定的先後順序的,我在進行區間覆蓋時,會直接將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;
}