珂朵莉天下第一!
咳咳,回归正题
OTD(Old Driver Tree,又称珂朵莉树)
名字挺奇怪的(指老司机树)
最开始起源于CF896C,本来使用线段树做的,但是有一位大神用了一种我们从未见过的方法过掉了,所以我们就为这种方式命名了
至于为什么是树呢,因为它主要是set,set又是平衡树实现的,所以就假装他是树吧(虽然set有点慢,但你也可以自己手写二叉平衡树来优化)
前置知识
会用STL的set,并且有手就行
核心思想
把数值相同的,相邻的数据合并在一起并存在set里,当然如果极端情况,不相邻的话,和数组差不多了
用途
解决有区间赋值的东西(因为这样相邻的相同的就比较多),并且数据随机的情况下(只要出题人想,你能T飞)时间复杂度比较可观
用 set 实现的珂朵莉树的复杂度为O(NloglogN) ,而用链表实现的复杂度为 (NlogN)。
以下以原题为例CF896C Willem, Chtholly and Seniorious
具体实现
结点保存
因为我们有时候set里的值会改变(区间加法)但是set里面的值本来是无法改动的,所以我们引入了mutable就可以改动set的元素啦(就和const是反着来的)
struct node{ int l,r;//左右区间 mutable ll v;//区间值 node(int l,int r=-1,ll v=0):l(l),r(r),v(v){}//构造函数 bool operator < (const node &x)const{//重载运算符 return l<x.l; } };
当然,要加其他数据都可以
分裂
这里就是珂朵莉树的核心操作了(就是裂开)
因为当我们做操作的时候,左右结点很难都是正好落在连续区间的端点上,所以有时候我们必须得把它分成两个区间,然后方便集体做操作(不要说直接从l开始到r,那样的话就是数组了,没意义)
IT split(int mid){//从 mid分开 IT it=s.lower_bound(node(mid));//查找第一个大于等于的区间 if(it!=s.end()&&it->l==mid)return it;//如果等于,说明正好落在端点上,就不用分裂 it--;//否则,就是大于,所以在前一个区间 int l=it->l,r=it->r; ll v=it->v; s.erase(it);//删除区间 s.insert(node(l,mid-1,v));//分成两半 return s.insert(node(mid,r,v)).first;//set的insert返回的是pair,其中前一个代表当前的指针 }
区间赋值
也是珂朵莉树的一大核心操作(你只分不合,set分分钟BOOM!!!)。还是那句话,左右端点不确定,所以先分裂
void assign(int L,int R,int v){ IT r=split(R+1),l=split(L);//分裂开 s.erase(l,r);//区间删除,只会删除到r-1的地方 s.insert(node(L,R,v));//加入区间 }
为什么传入的时候是R+1呢
此时,我们分裂r:L-R,r就是mid,因为我们分裂是[L,mid-1]和[mid,R],所以分裂后就变成了
然后删除后返回的又是下一个区间[mid,R],删除的时候因为只会删除到前一个区间,所以最后就变成了这样
这这这,明显不是我们想要的嘛,所以传入参数的时候要+1
剩下的区间加啊,第k小啊,什么次方和啊,分分分,暴力就完事儿了
区间加法
void add(int L,int R,int v){ IT r=split(R+1),l=split(L);//分分分 for(;l!=r;l++)//对于每一块的值加上去就行了(也就体现了mutable) l->v+=v; }
区间第k小
ll kth(int L,int R,int k){ IT r=split(R+1),l=split(L);//分分分 vectorint> > v;//排序暴力找就完事儿了 v.clear(); for(;l!=r;l++)v.push_back(pair int>(l->v,l->r-l->l+1)); sort(v.begin(),v.end());//排个序 for(vector int> >::iterator i=v.begin();i!=v.end();i++)//暴力找 { k-=i->second; if(k<=0)return i->first; } }
次方和
这个是线段树啥的实现不了的,所以暴力万岁!珂朵莉万岁!
加个快速幂就行
最后贴个代码
1 #include2 #define ll long long 3 #define IT set ::iterator 4 const int N=100010; 5 using namespace std; 6 int n,m,vmax; 7 int a[N]; 8 ll seed; 9 struct node{ 10 int l,r; 11 mutable ll v; 12 node(int l,int r=-1,ll v=0):l(l),r(r),v(v){} 13 bool operator < (const node &x)const{ 14 return l<x.l; 15 } 16 }; 17 set s; 18 ll rnd(){ 19 ll ans=seed; 20 seed=(seed*7+13)%1000000007; 21 return ans; 22 } 23 ll qpow(ll x,ll y,ll p){ 24 x%=p; 25 ll ans=1; 26 while(y) 27 { 28 if(y&1)(ans*=x)%=p; 29 y>>=1; 30 (x*=x)%=p; 31 } 32 return ans; 33 } 34 IT split(int mid){ 35 IT it=s.lower_bound(node(mid)); 36 if(it!=s.end()&&it->l==mid)return it; 37 it--; 38 int l=it->l,r=it->r; 39 ll v=it->v; 40 s.erase(it); 41 s.insert(node(l,mid-1,v)); 42 return s.insert(node(mid,r,v)).first; 43 44 } 45 void add(int L,int R,int v){ 46 IT r=split(R+1),l=split(L); 47 for(;l!=r;l++) 48 l->v+=v; 49 } 50 void assign(int L,int R,int v){ 51 IT r=split(R+1),l=split(L); 52 s.erase(l,r); 53 s.insert(node(L,R,v)); 54 } 55 ll kth(int L,int R,int k){ 56 IT r=split(R+1),l=split(L); 57 vector int> > v; 58 v.clear(); 59 for(;l!=r;l++)v.push_back(pair int>(l->v,l->r-l->l+1)); 60 sort(v.begin(),v.end()); 61 for(vector int> >::iterator i=v.begin();i!=v.end();i++) 62 { 63 k-=i->second; 64 if(k<=0)return i->first; 65 } 66 67 } 68 ll sum(int L,int R,int x,int y){ 69 IT r=split(R+1),l=split(L); 70 ll ans=0; 71 for (;l!=r;l++) 72 ans=((ans+(ll)(l->r-l->l+1)*qpow(l->v,(ll)x,(ll)y))%y+y)%y; 73 return ans; 74 } 75 int main() 76 { 77 scanf("%d%d%lld%d",&n,&m,&seed,&vmax); 78 for(int i=1;i<=n;i++)a[i]=rnd()%vmax+1,s.insert(node(i,i,a[i])); 79 while(m--) 80 { 81 int op,l,r,x,y; 82 op=rnd()%4+1,l=rnd()%n+1,r=rnd()%n+1; 83 if(l>r)swap(l,r); 84 if(op==3)x=rnd()%(r-l+1)+1; 85 else x=rnd()%vmax+1; 86 if(op==4)y=rnd()%vmax+1; 87 88 switch(op) 89 { 90 case 1:{ 91 add(l,r,x); 92 break; 93 } 94 case 2:{ 95 assign(l,r,x); 96 break; 97 } 98 case 3:{ 99 cout< endl; 100 break; 101 } 102 case 4:{ 103 cout< endl; 104 break; 105 } 106 } 107 } 108 return 0; 109 }
提示
实在想不出来正解了,才用珂朵莉树骗骗分,一般情况下还是正解。但珂朵莉树代码短,调试方便,不得不吹,实乃骗分利器!珂朵莉万岁!!!