1. 程式人生 > >淺談珂朵莉樹

淺談珂朵莉樹

## 什麼是珂朵莉樹 珂朵莉樹,又稱$Old Driver Tree(ODT)$(老司機樹)。 是一種基於$set$的~~暴力~~資料結構。 因此,再學習珂朵莉樹之前,要掌握一些$set$和迭代器的知識 ## 珂朵莉樹的適用範圍 線段樹能幹的它都能幹(~~只要你不怕T~~) 使一整段區間內的東西變得一樣,**資料隨機** 比如下面這一道題 ## 起源題:CF896C ### 題目描述 ![](https://cdn.luogu.com.cn/upload/image_hosting/1pqjy2jn.png) ### 分析 如果只有前$3$個操作,那麼別的資料結構似乎還可以使用 但是第$4$個操作是把區間中的每一個數拿出來進行運算 這樣的操作其它資料結構很難勝任 這時我們就要用到珂朵莉樹 ### 定義 珂朵莉樹是把連續的一段值相同的區間當作一個節點對待 因此節點定義如下 ``` cpp struct asd{ ll l,r; //節點的左右端點 mutable ll val; //節點的權值,如果不加mutable則在初始化後無法進行修改 bool operator < (const asd& A)const{ return l::iterator ``` 首先,我們要巨集定義$set$的迭代器 ~~如果你不怕麻煩每次手打也可以~~ 然後,我們再通過$set$建立一棵樹 ``` cpp set s; ``` (set是C++自帶的平衡樹,這就是珂朵莉樹是一棵樹的原因) 最後是程式碼 ``` cpp sit Split(ll wz){ //返回值型別為迭代器 sit it=s.lower_bound(asd(wz)); //查詢第一個左端點編號大於等於wz的節點 if(it!=s.end() && it->l==wz) return it; //如果該節點的左端點是我們要分裂的節點,直接返回 it--; //否則分裂上一個 ll l=it->l,r=it->r,val=it->val; s.erase(it); //將該節點拆分為兩個 s.insert(asd(l,wz-1,val)); return s.insert(asd(wz,r,val)).first; //返回分裂位置的迭代器 } ``` ### 複雜度的保證Assign 將一個區間推平,賦成相同的值 ``` cpp void Assign(ll l,ll r,ll val){ sit it2=Split(r+1),it1=Split(l); s.erase(it1,it2); //刪除區間[l,r+1)中所有的節點 s.insert(asd(l,r,val)); //插入新節點 } ``` ### 其它操作 ~~一個比一個暴力~~ #### 區間加 把$[l,r]$中的節點取出,分別加上就好了 這裡有一個細節必須注意,必須先宣告$it2$再宣告$it1$ 否則根據$split$中的$erase$操作,迭代器$it1$可能會失效。 (因為$it1$所屬的節點可能被刪除了) ``` cpp void ad(ll l,ll r,ll val){ sit it2=Split(r+1),it1=Split(l); for(sit it=it1;it!=it2;++it){ it->
val+=val; } } ``` #### 區間第k小 把$[l,r]$中的節點取出,$sort$一下就行了 ``` cpp ll kth(ll l,ll r,ll k){ sit it2=Split(r+1),it1=Split(l); vector >a; a.clear(); for(sit it=it1;it!=it2;it++){ a.push_back(make_pair(it->val,it->r-it->l+1)); } sort(a.begin(),a.end()); for(ll i=0;i>=1; } return ans%mod; } ll cx(ll l,ll r,ll x,ll y){ sit it2=Split(r+1),it1=Split(l); ll ans=0; for(sit it=it1;it!=it2;it++){ ans=(ans+(it->r-it->l+1)*ksm(it->val,x,y)%y)%y; } return ans; } ``` ### 完整程式碼 ``` cpp #include
using namespace std; typedef long long ll; #define sit set::iterator const ll maxn=1e6+5; struct asd{ ll l,r; mutable ll val; bool operator < (const asd& A)const{ return l s; sit Split(ll wz){ sit it=s.lower_bound(asd(wz)); if(it!=s.end() && it->l==wz) return it; it--; ll l=it->l,r=it->r,val=it->val; s.erase(it); s.insert(asd(l,wz-1,val)); return s.insert(asd(wz,r,val)).first; } void Assign(ll l,ll r,ll val){ sit it2=Split(r+1),it1=Split(l); s.erase(it1,it2); s.insert(asd(l,r,val)); } void ad(ll l,ll r,ll val){ sit it2=Split(r+1),it1=Split(l); for(sit it=it1;it!=it2;++it){ it->val+=val; } } ll kth(ll l,ll r,ll k){ sit it2=Split(r+1),it1=Split(l); vector >a; a.clear(); for(sit it=it1;it!=it2;it++){ a.push_back(make_pair(it->val,it->r-it->l+1)); } sort(a.begin(),a.end()); for(ll i=0;i>=1; } return ans%mod; } ll cx(ll l,ll r,ll x,ll y){ sit it2=Split(r+1),it1=Split(l); ll ans=0; for(sit it=it1;it!=it2;it++){ ans=(ans+(it->r-it->l+1)*ksm(it->val,x,y)%y)%y; } return ans; } ll n,m,mmax,seed; ll rnd(){ ll ret=seed; seed=(seed*7+13)%1000000007; return ret; } int main(){ scanf("%lld%lld%lld%lld",&n,&m,&seed,&mmax); for(ll i=1;i<=n;i++){ ll aa=rnd()%mmax+1; s.insert(asd(i,i,aa)); } s.insert(asd(n+1,n+1,0)); for(ll i=1;i<=m;i++){ ll l,r,x,y; ll op=rnd()%4+1; l=rnd()%n+1,r=rnd()%n+1; if(l>r) swap(l,r); if(op==3) x=rnd()%(r-l+1)+1; else x=rnd()%mmax+1; if(op==4) y=rnd()%mmax+1; if(op==1) ad(l,r,x); else if(op==2) Assign(l,r,x); else if(op==3) printf("%lld\n",kth(l,r,x)); else printf("%lld\n",cx(l,r,x,y)); } return 0; } ``` ### 複雜度證明 [傳送門](https://zhuanlan.zhihu.com/p/102786071) ## 其它題目 CF343D Water Tree(珂朵莉樹上樹) CF915E Physical Education Lessons(正解為動態開點線段樹) P4979 礦洞:坍塌 P1558 色板遊戲 P3740 貼海報 P5350 序列 P1204 擠牛奶Milking Cows