首页 > 其他分享 >珂朵莉树

珂朵莉树

时间:2024-10-04 22:22:53浏览次数:8  
标签:itl int ll pos iter 朵莉树 split

吾日三省吾身:末日时在干什么?有没有空?可以来拯救一下吗?

算法思想

非常简单:就是暴力。

对于数据结构题,我们有这样一种思路去维护:对于一个数列,我们把不同的数字看成不同的颜色段,然后对每个颜色段进行暴力操作,可以有效降低时间复杂度。但这种暴力是很好卡掉的,只需让颜色段尽可能多,算法就可以直接退化的 \(O(n^2)\),甚至在随机数据下,这种算法的表现依然不是很好。

但在特定的情况下,当题目中的颜色段很少(例如:存在区间赋值操作且数据随机),这种算法就可以达到 \(O(n\log n)\) 等极好的时间复杂度,珂朵莉树,也叫老司机树(英文:Old Drive Tree,简写:ODT)应运而生。可以支持许多复杂的操作。

算法讲解

模板:CF896C Willem, Chtholly and Seniorious

题意简述:

写一个数据结构,支持以下操作:

  • 1 l r x:将\([l,r]\) 区间所有数加上\(x\)
  • 2 l r x :将\([l,r]\) 区间所有数改成\(x\)
  • 3 l r x :输出将\([l,r]\) 区间从小到大排序后的第\(x\) 个数是的多少(即区间第\(x\) 小,数字大小相同算多次,保证 \(1\leq\) \(x\) \(\leq\) \(r-l+1\))
  • 4 l r x y :输出\([l,r]\) 区间每个数字的\(x\) 次方的和模\(y\) 的值,即\(\left(\sum^r_{i=l}a_i^x\right)\)\(\bmod y\)。

操作用普通的数据结构难以维护,同时存在区间赋值操作,启发我们使用珂朵莉树。

珂朵莉树实际上就是一个 set,set 中的每个节点都是一个颜色段。

struct cht{
	int l,r;
	mutable ll v;//用于快速修改颜色
	cht(int L,int R,ll V){
		l=L,r=R,v=V;
	}
	bool operator < (const cht &a)const {
		return l<a.l;
	}//默认按照颜色段左端点排序
};
typedef set<cht>::iterator iter;//迭代器,就是指针,接下来的各种操作都会常用
set<cht>s;

操作 \(1\):split

这是珂朵莉树中最为重要的操作之一。

我们定义 split(pos) 表示返回以 \(pos\) 为左端点的颜色段的指针。特别的,如果 \(pos\) 位于颜色段 \(l,r\) 内,并非是端点,则将 \([l,r]\) 分裂为 \([l,pos-1],[pos,r]\) 并返回后者的指针;如果不存在 \(pos\) 这个位置,则返回 set 的尾指针。

步骤其实十分简单:

  1. 找到 \(pos\) 所在颜色段。

  2. 如果 \(pos\) 是该颜色段的端点,直接返回指针

  3. 分裂,返回分裂后的结果

iter split(int pos){
	iter it=s.lower_bound(cht{pos,0,0});//查找颜色段
	if(it!=s.end()&&it->l==pos)return it;//判断是否是端点,前面的特判是为了避免不存在 pos 导致 RE
	it--;//此时的 it 指向的颜色段包含 pos
	if(it->r<pos)return s.end();//特判 pos 是否存在
	int L=it->l,R=it->r;ll V=it->v;
	s.erase(it);//分裂
	s.insert(cht{L,pos-1,V});
	return s.insert(cht{pos,R,V}).first; //insert 返回的 pair 的第一个参数是新插入的位置的迭代器
}

操作 \(2\):assign

区间推平,同样也是极其重要的操作之一。

我们要做的就是先分裂出以 \(l,r\) 为端点的颜色段,记为 \(s,t\),然后将编号 \([s,t)\) 以内的所有颜色段删除,然后再插入一个以 \([l,r]\) 为端点的大颜色段

set 支持删掉某个范围内的所有元素,调用 erase(s,t) 即可删除 set 中所有位于 \([s,t)\) 的元素。

void assign(int l,int r,ll k){
	iter itr=split(r+1),itl=split(l);//r+1 是因为 erase 函数遵循左闭右开原则
	s.erase(itl,itr);
	s.insert(cht{l,r,k});  
}

注意到我们先调用了 split(r+1) 后再调用了 split(l)。这个顺序是不可改变的。因为具体解释比较麻烦,所以我盗了一张大佬的图(by user43206)

原因图

注意到调用 split(l) 后再调用 split(r+1) 可能会导致 \(itl\) 原本指向的颜色段被删除分裂成两个,所以我们必须按照正确的顺序分裂。

其他操作

其实非常简单,就是分裂出左右颜色段,然后暴力遍历其中的所有颜色段.....

是的,就是这么简单,给出一份伪代码

change(int l,int r,...){
    iter itr=split(r+1),itl=split(l);
	for(iter it=itl;it!=itr;it++){
		/*
        do something
        */
	}
    /*
    do something
    */
}

区间加

void add(int l,int r,ll k){
	iter itr=split(r+1),itl=split(l);
	for(iter it=itl;it!=itr;it++){
		it->v+=k;//可以直接改变结构体的成员是因为使用了 mutable
	}
	return;
}

区间第 \(k\) 小

#define pli pair<ll,int>
#define mp make_pair
typedef long long ll;
pli bk[N];
int tot;
ll kth(int l,int r,int rk){
	tot=0;
	iter itr=split(r+1),itl=split(l);
	for(iter it=itl;it!=itr;it++){
		bk[++tot]=mp(it->v,it->r-it->l+1);
	} 
	sort(bk+1,bk+1+tot);
	for(int i=1;i<=tot;i++){
		if(rk<=bk[i].second)return bk[i].first;
		rk-=bk[i].second;
	}
    return -1;
} 

区间幂的和

ll ksm(ll a,ll b,ll p){
	ll ans=1;
	while(b){
		if(b&1)ans=(ans*a)%p;
		a=(a*a)%p;
		b>>=1;
	}
	return ans;
}
ll pow_sum(int l,int r,ll x,ll y){
	iter itr=split(r+1),itl=split(l);
	ll res=0;
	for(iter it=itl;it!=itr;it++){
		res+=(it->r-it->l+1)*ksm(it->v%y,x,y)%y;
        res%=y;
	} 
    return res;
} 

然后这题就做完了,提交记录

时间复杂度

不会证,但是是神奇的 \(O(n\log n)\)。

例题

鸽,以后补几道。

The End

图

「最喜欢珂朵莉了」

标签:itl,int,ll,pos,iter,朵莉树,split
From: https://www.cnblogs.com/zuoqingyuan114/p/18447399

相关文章

  • 珂朵莉树
    经典题(珂朵莉树的诞生):https://www.luogu.com.cn/problem/CF896C参考:https://www.luogu.com.cn/article/yfcae6d6不用知道它的具体复杂度,主要是学习如何实现。珂朵莉树(ChthollyTree),又名老司机树ODT(OldDriverTree)。splitpos区间分割操作,也就是将包含pos的区间分割为两端......
  • 珂朵莉树/颜色段均摊学习笔记
    珂朵莉树是基于set的数据结构,与线段树、平衡树等树形结构类似,珂朵莉树是用来解决区间问题的很暴力的树形结构该数据结构支持区间推平,即将区间\([l,r]\)赋值为同一个值1.前置知识setset本质是平衡树,所以不会出现重复的元素且会自动排序,部分函数:set<Node>t;//定......
  • 【学习笔记】珂朵莉树
    前言珂朵莉树(ChthollyTree),又名老司机树(OldDriverTree),起源自CF896CWillem,ChthollyandSeniorious。严格来说,珂朵莉树这种想法是基于数据随机的颜色段均摊,而不是一种数据结构,可作为一些题目的暴力做法(因此原题被分到了暴力数据结构的标签),在随机数据下一般效率较高。基......
  • 洛谷 CF896C Willem, Chtholly and Seniorious之珂朵莉树板子
    洛谷CF896C题解传送锚点摸鱼环节Willem,ChthollyandSeniorious题面翻译【题面】请你写一种奇怪的数据结构,支持:\(1\)\(l\)\(r\)\(x\):将\([l,r]\)区间所有数加上\(x\)\(2\)\(l\)\(r\)\(x\):将\([l,r]\)区间所有数改成\(x\)\(3\)\(l\)\(r\)\(x\):输出将\(......
  • 【模板】珂朵莉树
    这是即将推送到OI-wiki的,对于原OI-wiki珂朵莉树页面的重构。作者是我。感谢上一位维护这玩意的人。简介珂朵莉树(ChthollyTree),又名老司机树ODT(OldDriverTree)。起源自CF896C。这个名称指代的是一种“使用平衡树(std::set、std::map等)或链表(std::list、手写链表等)维护颜......
  • [OI] 珂朵莉树
    对于一个序列,它有较多重复元素,并且题目需要维护区间修改,维护区间信息,维护整块值域信息的,那么就可以考虑珂朵莉树解决.主要思想珂朵莉树将全部相同的颜色块压缩为一组,如对于下述序列:111234444珂朵莉树铺平后即可以变为这样:{1,3,1}{4,4,2}{5,5,3}{6,9,4}其中的三......
  • [数据结构] 珂朵莉树
    介绍珂朵莉树,学名珂朵莉树,又学名老司机树($ODT$),常用来解决“区间推平”(把区间改为某一个数)等的操作,只适用于随机数据,可以定向卡掉;其实这玩意就是暴力,没啥可说的,分块都比不上她暴力;但人家毕竟是她,所以对于一些题还是有用的;实现只要你会$C++\STL$里的$set$,搞定她完......
  • 算法学习笔记(18):珂朵莉树
    珂朵莉树这个名字我猜是来源于初次诞生这个算法的题目->Willem,ChthollyandSeniorious算法适用于数据随机,并且有区间推平操作,也就是区间赋值操作,就可以用set维护,达到优秀的\(O(nlogn)\)时间复杂度。定义structNode{ intl,r; mutableintv; Node(intl,intr......
  • 珂朵莉树
    一种特殊的数据结构。介绍李欣隆发明。本质上并不是一种树形数据结构,思想是把一个区间按\(value\)划分成不少的小区间,然后暴力操作。显然,这样做的复杂度完全依赖于小区间的多少,所以复杂度需要由区间推平操作保证。在随机的数据下,可以证明每次的小区间期望有\(\log_2n\)个,......
  • 珂朵莉树ODT 模板
    构造set:structnode{intl,r;mutableintv;node(intl,intr,intv=0):l(l),r(r),v(v){}booloperator<(constnode&a)const{returnl<a.l;}};set<node>s;分裂set:autosplit(intpos){aut......