珂朵莉樹:獻給世界上最幸福的女孩


 引言

 所以,我敢肯定,現在的我.不管別人怎么說,都一定是世界上最幸福的女孩。

                         ——Chtholly·Nota·Seniorious


 前言

  珂朵莉樹(Chtholly_Tree),原名老司機樹(Old Diver Tree,ODT),但是叫做珂朵莉樹是廣大 珂學家們 Oiers所喜聞樂見的,所以我們一般叫她珂朵莉樹。發明者是著名的 毒瘤lxl同志。

  如果想了解珂朵莉樹的前世今生,請自行百度。

  珂朵莉很強,珂朵莉樹更強,簡直是區間維護的一姐(個人觀點勿噴awa)

 


 適用范圍

  1. 有推平操作(區間賦值),且數據隨機(不過出題人一般不會惡意卡她)

  2. 尤其是有許多奇怪操作難以維護的題目(當然也不一定要有...)

   (開了O2就放心用吧)

  舉個栗子:

  下面是在luogu上的一些可以用珂朵莉樹AC的題目

  

  黑 (NOI/NOI+/CTSC)

       CF896C Willem, Chtholly and Seniorious
       P3215 [HNOI2011]括號修復 & [JSOI2011]括號序列

       紫 (省選/NOI-)
       P2787 語文1(chin1)- 理理思維
       CF915E Physical Education Lessons
       P2572 [SCOI2010]序列操作
       P4979 礦洞:坍塌
       P4344 [SHOI2015]腦洞治療儀

       藍 (提高+/省選-)
       P2894 [USACO08FEB]酒店Hotel

      綠 (普及+/提高)
      P3740 [HAOI2014]貼海報

      黃 (普及/提高-)
      P2082 區間覆蓋(加強版)
      U59472 校門外的樹plus

      橙 (普及-)
      P1496 火燒赤壁
      P1204 [USACO1.2]擠牛奶Milking Cows

      紅 (入門)

      P1047校門外的樹

 可見其適用性之廣。可以說,只要是有區間賦值操作的區間維護問題,幾乎都可以用珂朵莉樹完成。


 正式介紹珂朵莉樹

  首先她是建立在 STL::set 的基礎上的一種暴力數據結構,基本思路是把元素值同為v的區間 [l,r] 表示成三元組(l,r,v)並用set維護它們。

  珂朵莉樹的各個區間一定不重不漏的覆蓋全集 

基♂操

定義set

  按照我的習慣,自然是要先上代碼的

struct node{
    int l,r;
    mutable int v; //mutable表示v可以被修改
    node(int l,int r=-1,int v=0):l(l),r(r),v(v){}
    bool operator <(const node& o)const{
        return l<o.l;
    }
};
set<node>s;
#define It set<node>::iterator //方便

當然,也可以這么寫

friend bool operator < (node a,node b){
  return a.l<b.l;      
}
typedef set<node>::iterator It;

至於構造函數,隨你怎么寫。


 Split 分割區間

聯系分塊,操作時把一個區間破成幾份是常有的事兒。

It split(int pos){
    It it=s.lower_bound(pos); 
    if(it!=s.end()&&it->l==pos)return it;
    --it;
    int l=it->l,r=it->r,v=it->v;
    s.erase(it);
    s.insert(node(l,pos-1,v));
    return s.insert(node(pos,r,v)).first;
}

 分割區間使得pos在一個區間的左端點並返回該區間的指針。

lower_bound找后繼,pos會被轉換為node(pos),找不到返回s.end()

用upper_bound也行,但是要跟着修改 ++it 和 其他操作都要先 split(l)后split(r+1),這個看后面。

如果已經滿足,返回;

否則一定在上一個區間。 (看看上面的紅字)

刪除,增加就行了。

s.insert()的返回值是pair類型,其first為指向新結點的指針


Assign推平操作

void assign(int l,int r,int k){
    It itr=split(r+1),itl=split(l);
    s.erase(itl,itr);
    s.insert(node(l,r,k));
}

十分簡單。不過注意如果先l再r可能會使l所在的塊被分割,使得

有點像splay的刪除,兩頭一夾,中間刪除。

最后再補上一塊兒。

s.erase(itl,itr)  刪除[itl,itr)的元素


Sum區間求和

int sum(int l,int r){
    It itr=split(r+1),itl=split(l);
    int res=0;
    for(It it=itl;it!=itr;++it)
        res+=it->v*(it->r-it->l+1);
    return res;
}

.for(It it=itl;..;..)可以寫成for(;itl!=itr;++itl)

..好簡單 = = (可真暴力)

不說了,不過注意在循環時使用 != 所以itr訪問不到。


 Add區間加

void add(int l,int r,int V){
    It itr=split(r+1),itl=split(l);
    for(;itl!=itr;++itl)itl->v+=V;
}

區間乘等以此類推。這里用了itl的循環寫法。


玄學操作

  一個比一個暴力,但是就是跑的飛快 = =

Reverse 區間取反(維護01序列)

void reverse(int l,int r){
    It itr=split(r+1),itl=split(l);
    for(It it=itl;it!=itr;++it)it->v^=1;
}

顯然。證畢。 = =


 Invent區間取負

void invert(int l,int r){
    It itr=split(r+1),itl=split(l);
    for(;itl!=itr;++itl)
        itl->v=-itl->v;
}

同上。得證。= =


 Swap區間翻轉(你沒看錯,這不是splay的函數)

void swap(int l,int r){
    vec.clear();
    It itr=split(r+1),itl=split(l);
    for(It it=itl;it!=itr;++it)
        vec.push_back(make_pair(it->v,it->r-it->l));
        //此處千萬不能改變itl , for(;itl!=itr;++itl)
    s.erase(itl,itr);
    for(vector<pair<int,int> >::iterator it=vec.end()-1;it!=vec.begin()-1;--it)
        s.insert(node(l,l+it->second,it->first)),
        l+=it->second+1;
}

這里用一個vector來裝pair類型,分別是值和區間長度。

最后反過來按照區間長度和當前的 l或r 計算區間位置即可。用l和r都行。


 Cont查詢最長連續區間

int cont(int l,int r){
    It itr=split(r+1),itl=split(l);
    int res=0,Maxn=0,v=-1;
    for(It it=itl;it!=itr;++it){
        if(v==-1||it->v==v)res+=(it->r-it->l+1);
        else Maxn=max(Maxn,res),res=0,v=it->v;
    }
    return max(Maxn,res);
}

當然你可以進行魔改(反正本來這些函數都是魔改的產物)搞成查詢指定v的連續區間、不同v的區間的個數、返回該區間的v等等...= =


 Kth區間第k小(這里真不是splay... = =)

LL Kth(int l,int r,int x){ //第x小
    vector<pair<LL,int> >vec; vec.clear();
    It itr=split(r+1),itl=split(l);
    for(;itl!=itr;++itl)
        //vec.push_back(make_pair(itl->v,itl->r-itl->l+1));
        vec.push_back(pair<LL,int>(itl->v,itl->r-itl->l+1));
    sort(vec.begin(),vec.end());
    for(vector<pair<LL,int> >::iterator it=vec.begin();it!=vec.end();++it)
        if(x<=it->second)return it->first;
        else x-=it->second;
    return -1LL; //cannot find
}

直接sort,怕什么qwq

懶得改LL和x小什么的了,求k大,求某數排名都差不多的.....


Sum區間冪次和(沒有函數名可以用了,將就一下吧qwq)

LL sum(int l,int r,int x,int y){
    It itr=split(r+1),itl=split(l);
    LL res=0;
    for(;itl!=itr;++itl)
        (res +=  (LL)power(itl->v,x,y) * (itl->r-itl->l+1)%y )%=y;
    return res;
}

這里是區間x次冪在mod y。

快速冪自己寫= =


常見的就這些(霧)但是還可以寫很多奇怪的函數,比如 P4344 [SHOI2015]腦洞治療儀里面的求前若干個零 ... etc

作為暴力結構我們要有暴力的信心,題目什么要求都能魔改出來,時刻記住,你是暴力,你比別人好調試  = = 還比別人短

就算你不相信自己,也要相信珂朵莉。

 

雖然說是這么說,但是基礎的優化還是要有的。


玄學優化:Merge區間(隨機)合並

先別急抄這個代碼,下面還有。

void merge(It it){
    It itl=it,itr=it;
    --itl;++itr;
    if(itl->v==it->v&&it->v==itr->v)assign(itl->l,itr->r,it->v);
    else if(itl->v==it->v)assign(itl->l,it->r,it->v);
    else if(itr->v==it->v)assign(it->l,itr->r,it->v);
}

每個函數結束,只要不影響,都調用一下。

只要能多合並幾次你就賺了。

有時有奇效。

這里也可以判斷 it != s.end()/s.begin()

不過用assign會慢(而且就不能在assign里面調用了qwq)

 

於是有(更優秀qwq)

void merge(It it){
    It itl=it,itr=it;
    --itl;++itr;
    if(it==s.begin()||it==s.end())return;
    if(itl->v==it->v&&it->v==itr->v){
        int l=itl->l,r=itr->r,v=it->v;
        s.erase(itl,++itr);
        s.insert(node(l,r,v));
    }else if(itl->v==it->v){
        int l=itl->l,r=it->r,v=it->v;
        s.erase(itl,itr);
        s.insert(node(l,r,v));
    }else if(itr->v==it->v){
        int l=it->l,r=itr->r,v=it->v;
        s.erase(it,++itr);
        s.insert(node(l,r,v));
    }
}

 


 通常的 建樹方式

s.insert(i,i,a[i]);

顯然這很慢 ...qwq

 

一般這樣做:

for(int i=2,L=1;i<=n+1;++i)
        if(i==n+1||a[i]!=a[i-1])
            s.insert(node(L,i-1,a[i-1])),L=i;

 

當然有些題目可以全部賦初值....= = 就是 s.insert(node(1,n,0))


 注意事項

  某些題目區間長度會比較奇怪(如P1496 火燒赤壁),當 l=r 時題目視作不存在

這時如果考慮修改各個函數則會非常麻煩,甚至導致奇怪的錯誤,於是,使用 s.insert(node(l,r-1))即可...

相當於把 [l,r) 作為 [l,r-1] 存儲,沒有問題。


End完結撒花~ = =

  還有不懂的可以在 評論 / QQ263863316 / 洛谷lsy263 詢問~ qwq 

希望大家都能學會這個好聽,好寫,好用的數據結構!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM