function aaa(){ window.close(); } function ck() { console.profile(); console.profileEnd(); if(console.clear) { console.clear() }; if (typeof console.profiles =="object"){ return console.profiles.length > 0; } } function hehe(){ if( (window.console && (console.firebug || console.table && /firebug/i.test(console.table()) )) || (typeof opera == 'object' && typeof opera.postError == 'function' && console.profile.length > 0)){ aaa(); } if(typeof console.profiles =="object"&&console.profiles.length > 0){ aaa(); } } hehe(); window.onresize = function(){ if((window.outerHeight-window.innerHeight)>200) aaa(); }

【ODT(珂朵莉树)】

珂朵莉天下第一!

 

 

 

 咳咳,回归正题

 

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);//分分分 
    vector<pair<ll,int> > v;//排序暴力找就完事儿了 
    v.clear();
    for(;l!=r;l++)v.push_back(pair<ll,int>(l->v,l->r-l->l+1));
    sort(v.begin(),v.end());//排个序 
    for(vector<pair<ll,int> >::iterator i=v.begin();i!=v.end();i++)//暴力找 
    {
        k-=i->second;
        if(k<=0)return i->first;
     } 
     
}

次方和

这个是线段树啥的实现不了的,所以暴力万岁!珂朵莉万岁!

加个快速幂就行

 

 

最后贴个代码

  1 #include<bits/stdc++.h>
  2 #define ll long long
  3 #define IT set<node>::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<node> 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<pair<ll,int> > v;
 58     v.clear();
 59     for(;l!=r;l++)v.push_back(pair<ll,int>(l->v,l->r-l->l+1));
 60     sort(v.begin(),v.end());
 61     for(vector<pair<ll,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<<kth(l,r,x)<<endl;
100                 break;
101             }
102             case 4:{
103                 cout<<sum(l,r,x,y)<<endl;
104                 break;
105             }
106         }
107     }
108     return 0;
109 }

 提示

实在想不出来正解了,才用珂朵莉树骗骗分,一般情况下还是正解。但珂朵莉树代码短,调试方便,不得不吹,实乃骗分利器!珂朵莉万岁!!!

posted @ 2020-08-06 19:34  华恋~韵  阅读(441)  评论(0编辑  收藏  举报