珂朵莉树总结

珂朵莉树总结

前置知识

set

简介

~~~~ 珂朵莉树(Chtholly Tree),又名ODT。起源自 CF896C(某位毒瘤出的题目。
~~~~~~ 说是树,其实跟树没有半毛关系,只是一种暴力的数据结构(甚至都不一定可以称之为数据结构),本质是基于数据随机的「颜色段均摊」。

使用前提

​ ​ ~~ 保证数据随机,且拥有区间推平,否则会退化成 O(n^2) 暴力。如果满足两个条件,它的时间复杂度会趋近 O(nloglogn) ,比线段树都要快很多。但由于其特性,也会被出题人精心制作的数据卡超时(其实都不用精心制作),因此一般用来骗分

基本原理

~~~~ 把值相同的区间合并成一个节点保存在 set 里面。

实现

节点存储

​​ ~~~~ 我们定义一个结构体来存储每个节点,用结构体变量来表示这个节点(区间)的值。

typedef long long ll;
struct  node{
    int l,r;//起点和终点
    mutable ll v;
    node(int L,int R=0,ll V=0):l(L),r(R),v(V){}
    bool operator <(const node& a)const{return l<a.l;}//按左端点排序
};

​​ ~~~~ 使用 mutable 是为了突破 const 限制,便于我们能在 set 中利用迭代器修改它。

构造珂朵莉树

set<node> chtolly;

​​ ~~~~ 就是这么简单,你得到了一个,没有初始化的珂朵莉树。

​​ ~~~~ 一般来讲,我们要向其中不断插入长度为1的节点来初始化。

for(int i=1;i<=n;i++){
    	cin>>a[i];
        chtolly.insert(node(i,i,a[i]));
    }
    chtolly.insert(node(n+1,n+1,0));

​​ ~~~~ 后续需要使用迭代器,懒的话可以直接宏定义。

#define IT set<node>::iterator

核心操作

分裂(split)

​​ ~~~~ 既然需要进行区间操作,我们就把需要修改的区间拿出来,有一部分需要修改,而另一部分不需要修改,就把这个区间拆开,要修改的部分就直接改,不需要改的就蒜了(听起来就很暴力)。

​​ ~~~~ 所以 split(pos) 的操作就是把 [l,r] 分裂成 [l,pos-1],[pos,r] ,返回后者的迭代器。

IT split(int pos){ //分裂
    IT it=chtolly.lower_bound(node(pos));//找到首个不小于pos的的节点
    if(it !=chtolly.end()&&it->l==pos){return it;}//左端点恰好在pos,无需分裂
    it--;//否则肯定在上一个结点
    int l=it->l,r=it->r;
    ll v=it->v;
    chtolly.erase(it);//清除原节点
    chtolly.insert(node(l,pos-1,v));
    return chtolly.insert(node(pos,r,v)).first;
}

​​ ~~~~ 这样,如果我们要利用区间中的数据就可以这样写了。

IT itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++){
   //你想干的事情
}

​​ ~~~~ 重要细节:必须先声明 itr ,再声明 itl ,否则 itl 可能会因为 split 中的 erase 操作而失效并导致 RE

​​ ~~~~ 当然只有 split 复杂度还是得爆炸,所以接下来就是珂朵莉树最核心(玄学)的部分。

推平(assign)

​​ ~~~~ 既然一个区间的值都一样了,那么我们就可以让这个区间用一个节点表示。珂朵莉树的核心。

void assign(int l,int r,int v){//推平
    IT itr=split(r+1),itl=split(l);
    chtolly.erase(itl,itr);
    chtolly.insert(node(l,r,v));
}

​​ ~~~~ 珂朵莉树的复杂度是由 assign 保证的。它可以让 set 大小快速下降,使珂朵莉树在随机情况下能超越线段树以及常数很大的数据结构

其他操作

​​ ~~~~ 都是暴力了,没什么好说的。

1.区间加

​​ ~~~~ 直接加一遍就好了(比较像分块)。

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

2.区间和

​​ ~~~~ 也是直接求和就好了。

ll sum(int l,int r)
{
    IT itr = split(r+1),itl=split(l);
 	ll res=0;
    for (;itl!=itr;itl++){
		int L=itl->l,R=itl->r;
    	ll V=itl->v;
        res+=(ll)(R-L+1)*V;
    }
    return res;
}

​ 珂朵莉树就介绍到这里了。制作不易,喜欢的记得点个赞。

彩蛋

我永远喜欢珂朵莉!!!

如果幸福有颜色,那一定是终末之红染尽的湛蓝。



“欢迎回来,珂朵莉。”

12 个赞

orz

3 个赞

顶一下吧,写了一晚上的,不会没人看吧 :technologist:

5 个赞

%%%

2 个赞

哇哦,好厉害,给你点个赞吧

8 个赞