珂朵莉树/ODT 学习笔记

珂朵莉树/ODT 学习笔记

起源自 CF896C。珂朵莉yyds!

核心思想

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

用处

骗分。只要是有区间赋值操作的数据结构题都可以用来骗分。在数据随机的情况下一般效率较高,但在不保证数据随机的场合下,会被精心构造的特殊数据卡到超时。

如果要保证复杂度正确,必须保证数据随机。详见 Codeforces 上关于珂朵莉树的复杂度的证明

更详细的严格证明见 珂朵莉树的复杂度分析。对于 add,assign 和 sum 操作,用 set 实现的珂朵莉树的复杂度为 O ( n log ⁡ log ⁡ n ) O(n \log \log n) O(nloglogn),而用链表实现的复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn)

正文

存储:每个树上节点都是一个区间,因为权值相同,于是要记录 l , r , v a l l,r,val l,r,val

struct node{
	I l,r;
	mutable int val;//可变的 val ,这么写是习惯,为了后续修改/访问操作
	friend bool operator <(node a,node b){
		return a.l<b.l;}//在 set 里面,应该比较区间左端点。以便之后查找区间并且 split 。
};set<node>s;

核心操作:split ,含义:把原来为 [l,r] 的这个节点分离成 [l,x) [x,r] ,并且返回这个 [x,r] 区间的迭代器。(可以直接使用 lower_bound)

auto split(I x){
	auto it=s.lower_bound({x,0,0});//在 set 之中查找 x ,因为只比较左端点所以另外两项可以随便填
	if(it!=s.end()&&it->l==x)return it;//如果正好找到了一个左端点为 x 的就直接返回。
	--it;auto p=*it;s.erase(it);//迭代器向左移动一位的区间才有可能被分裂,用 p 记录该迭代器,并且删除现在代表的区间
	s.insert(node{p.l,x-1,p.val});//把这个区间(存在 p 里面了)分裂
	return s.insert(node{x,p.r,p.val}).first;//同上,返回右边所指的迭代器,insert 的返回值是一个 pair ,它的返回值 first 是迭代器,second 是一个 bool ?
}

核心操作:assign,含义:把区间 [l,r] 赋值成为 val 。

void assign(I l,I r,I x){
	auto itr=split(r+1),itl=split(l);//先分裂出 [r+1,+inf] 的右边界,才能分离出 [l,r] 的可行部分,itl->l 就是 l(这个写分裂合并数据结构的大概都知道)
	s.erase(itl,itr);//这个是前闭后开的,删除了 itl...itr-1 的区间、迭代器
	s.insert(node{l,r,x});//插入一个全新的区间
}

普通操作:直接从左往右遍历珂朵莉树。

void count(I l,I r){
	auto itr=split(r+1),itl=split(l);
	for(;itl!=itr;++itl)//这个也是前闭后开的
		//do something...
}

习题

P4979 矿洞:坍塌

题意:区间推平,单点查询,判断区间颜色是否统一。

做法

做这道题先来练手。

首先区间推平只需要 assign 即可。

单点查询?只需在 set 之中二分位置即可。

判断区间颜色是否单一?只需按照上文 count 操作即可。(还有点小优化:如果合法就可以直接统一为一个区间)


CF1638E Colorful Operations

题意:每个数有两种属性,一种颜色,一种权值。

区间颜色推平;全局某种颜色所有数的加上一个数;单点查询权值。

做法

首先观察因为颜色是不连续的区间,所以不可能一个个去查找并给它加上权值。

于是就给颜色 t t t 打上标记 b z [ t ] bz[t] bz[t] 表示当前 t t t 颜色操作之中累加到了多少,因为不断更新很费事。

a a a 数组就缓慢更新,只有改变了颜色才更新,这样一个节点的真实值就是 a [ i ] + b z [ c o l [ i ] ] a[i]+bz[col[i]] a[i]+bz[col[i]]

如何求一个 p p p c c c 颜色修改至 c ′ c' c 颜色对其答案的贡献呢?

因为它的真实值是 a [ i ] + b z [ c ] a[i]+bz[c] a[i]+bz[c] ,所以先算了 b z [ c ] bz[c] bz[c] 的帐,即加上 b z [ c ] bz[c] bz[c]

但是它不能直接存储真实值,所以要减去 b z [ c ′ ] bz[c'] bz[c]

在 ODT 上每个节点都是一段区间,所以考虑使用树状数组维护。

总而言之,操作1就珂朵莉树上面的 assign 操作和区间加;操作2直接 O(1) 改变标记;操作3就直接树状数组查询权值+珂朵莉树查询颜色。


CF343D Water Tree

题意:树上推平子树;树上推平路径;单点查询。

做法

观察到本题只有区间推平和单点查询操作,所以珂朵莉树维护。

树上的操作通过树剖即可解决。


6362.【NOIP2019模拟2019.9.18】数星星(star)

题意:给 n n n 个点有点权,有 m m m 条路径,有 q q q 次操作查询 [ l , r ] [l,r] [l,r] 条路径的并的点权之和是多少。

(编者强烈建议搞定 CF343D 和 CF1638E 再来处理本题)

做法

看这么一个东西:HH的项链,就是区间统计个数,相同的只统计一次。

因为可以离线,所以按照 r r r 递增排序,不断加入 r r r ,然后更新一下每个数最后出现在哪里(其实就是权值等于 a [ r ] a[r] a[r] 的数)。对于 [ l , r ] [l,r] [l,r] 询问,查询最晚出现位置大于 l − 1 l-1 l1 的数的个数。

然后考虑迁移到这题之中:也是离线下来,按照 r r r 递增排序,不断加入 r r r ,然后更新一下每个点最后出现在第几个询问。对于 [ l , r ] [l,r] [l,r] 依旧是查询最晚出现位置大于 l − 1 l-1 l1 的点。对于一条路径更新路径上所有点最晚出现位置,查询所有最晚出现位置大于 l − 1 l-1 l1点权之和

再写得显然一些,树链剖分之后(即在 dfs 序上做区间操作),区间推平;查询大于等于 l l l 的数的权值之和(可以转化成小于等于 l − 1 l-1 l1 数的个数,初始出现位置 0 0 0 就好)。

这个大于等于 l l l 的数的点权之和就可以直接丢进树状数组里面处理;对于区间推平,直接使用 ODT 维护即可。因为 ODT 把每个区间压缩成为了一个点,所以加减的话也要直接加减这个区间的点权之和。因为是被推平,所以要减去之前的贡献,增加现在的贡献(和 CF1638E 一样)。

实现就显而易见了。


课后练习题

CF896C Willem, Chtholly and Seniorious 万 恶 之 源

CF915E Physical Education Lessons 比模板题还简单 都是暴力有什么难的

CF817F MEX Queries 和上题差不多,但是值域超大,珂朵莉树能做

P5350 序列 平衡树的题和珂朵莉树有什么关系?

CF1423G Growing flowers *3500?*800!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值