zoukankan      html  css  js  c++  java
  • 珂朵莉树:献给世界上最幸福的女孩

     引言

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

                             ——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 

    希望大家都能学会这个好听,好写,好用的数据结构!

  • 相关阅读:
    http服务详解(1)——一次完整的http服务请求处理过程
    Mysql数据库的二进制安装和基础入门操作
    DNS—正、反向解析;委派;主从;子域;转发;智能dns等的实现
    CA认证和颁发吊销证书
    Maven构建Web项目
    MySQL,查看连接数和状态等
    Maven编译出现“[ERROR] java.lang.OutOfMemoryError: Java heap space”
    Map集合
    List集合
    Myecplise反编译工具安装
  • 原文地址:https://www.cnblogs.com/lsy263/p/11710334.html
Copyright © 2011-2022 走看看