洛谷P4891 序列(勢能線段樹)

洛谷題目傳送門

閒話

考場上一眼看出這是個毒瘤線段樹準備槓題,發現實在太難調了,被各路神犇虐哭qwq

考後看到各種優雅的暴力AC。。。。。。寶寶心裏苦qwq

思路分析

題面裏面是一堆亂七八糟的限制和性質,這時候需要冷靜想想有沒有可利用的地方。蒟蒻一開始往勢能線段樹上面想了想。

定義一個全局勢能函數,爲所有\(C_i<B_i\)的位置個數。注意兩個操作的修改都不會小於原來的數。

一個是改\(A\),相當於對\(C\)進行區間設置,此時我們每暴力找到一個原來\(C_i<B_i\)但是現在\(C_i\ge y\)的位置,就需要在線段樹內跳\(\log\)層,再修改,勢能函數就會下降。如果碰到那些修改以後對勢能沒有影響的子區間,就跳過而不繼續暴力遞歸下去。

一個是改\(B\),是單點修改,線段樹內跳\(\log\)層,勢能函數至多加\(1\)

於是,如果我們能夠維護信息,從而判斷和控制哪裏該暴力遞歸、哪裏該跳過的話,我們總的在線段樹內跳的次數不會超過\((n+q)\log n\)

下面用a代替了\(C\),b代替了\(B\)。蒟蒻在線段樹裏維護了:

  • pa:區間所有a<b的位置的a的積
  • pb:區間所有a>=b的位置的b的積
  • ma:區間a的最大值
  • mb:區間所有a<b的位置的b的最小值,沒有則設成INF
  • cnt:區間所有a<b的位置的總數
  • la:bool型變量,區間設置懶標記

修改a的時候,利用單調不降的性質,我們在線段樹上先通過二分來對需要修改的若干個子樹進行定位。對於當前子區間,如果當前設置值\(y\)比mb要小,那麼設置對答案沒有影響,直接打上區間設置標記後退出;否則繼續遞歸直到找到葉子節點,進行修改後退出。

修改b就比較輕鬆,只要找到對應的葉子節點改完後一路回溯即可。

很多事都是說起來容易做起來難,這題也不例外。調試幾乎花了整個晚上。要注意的細節很多,也只好自己仔細思考了。

時間複雜度\(O(n\log n+q\log^2n)\),上界很鬆。多出來的\(\log\)是區間改a時需要快速冪更新pa造成的。

跑了不到200ms,比什麼樹套樹、分塊還是要好看一點,但是被暴力碾壓也是有點無奈啊~

#include<bits/stdc++.h>
#define RG register
#define R RG int
#define I inline
#define G if(++ip==ie)fread(ip=buf,1,N,stdin)
using namespace std;
typedef long long LL;
const LL N=1<<18,YL=1e9+7;
char buf[N],*ie=buf+N,*ip=ie-1;
int y,a[N];
I int in(){
    G;while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x*=10;x+=*ip&15;G;}
    return x;
}
I LL qpow(RG LL b,R k){//快速冪
    RG LL a=1;
    for(;k;k>>=1,(b*=b)%=YL)
        if(k&1)(a*=b)%=YL;
    return a;
}
struct Node{//個人認爲寫指針比較直觀(貌似immortalCO也有這樣的看法)
    Node*lc,*rc;bool la;
    LL pa,pb;int l,r,m,ma,mb,cnt;
    I void up(){//上傳
        pa=lc->pa*rc->pa%YL;
        pb=lc->pb*rc->pb%YL;
        ma=max(lc->ma,rc->ma);
        mb=min(lc->mb,rc->mb);
        cnt=lc->cnt+rc->cnt;
    }
    I void dn(){//下傳區間設置標記
        if(la){
            lc->ma=rc->ma=ma;
            lc->la=rc->la=1;la=0;
            lc->pa=qpow(ma,lc->cnt);
            rc->pa=qpow(ma,rc->cnt);
        }
    }
    I void build(R s,R e){//建樹
        l=s;r=e;m=(s+e)>>1;la=0;
        if(s==e){
            ma=a[l];mb=in();
            (cnt=ma<mb)?(pa=ma,pb=1):(pa=1,pb=mb,mb=YL);
            return;
        }
        (lc=new Node)->build(l,m);
        (rc=new Node)->build(m+1,r);
        this->up();
    }
    I void upda(){//區間修改a
        if(y<mb){//對區間勢能沒有影響
            pa=qpow(ma=y,cnt);la=1;
            return;
        }
        if(l==r){//到了葉子節點
            ma=y;pa=1;pb=mb;mb=YL;cnt=0;
            return;
        }
        this->dn();
        lc->upda();rc->upda();
        this->up();
    }
    I void updb(R s){//單點更新b
        if(l==r){//仔細判斷三種情況再修改
            if(ma<pb)mb=y;
            else if(ma<y)pa=ma,pb=cnt=1,mb=y;
            else pb=y;
            return;
        }
        this->dn();
        (s<=m?lc:rc)->updb(s);
        this->up();
    }
    I void bound(R s){//線段樹二分定位,注意細節
        if(s==l&&ma<y)return this->upda();
        if(l==r)return;
        this->dn();
        if(s>m)return rc->bound(s),this->up();
        if(lc->ma<y)rc->bound(m+1);
        lc->bound(s);
        this->up();
    }
};
int main(){
    R n=in(),q=in(),op,x;
    for(R i=1;i<=n;++i)a[i]=max(a[i-1],in());
    RG Node rt;rt.build(1,n);
    while(q--){
        op=in();x=in();y=in();
        op?rt.updb(x):rt.bound(x);
        printf("%lld\n",rt.pa*rt.pb%YL);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章