【学习笔记】珂朵莉树/颜色段均摊

# 壹:【知识点】

一:【定义】

珂朵莉树(Chtholly Tree)也叫老司机树(ODT),是一种基于平衡树(一般用 set 或 map 实现)的维护颜色段均摊的暴力技巧,而非一种数据结构。

其核心思想是将值相同的一段区间合并成一个结点处理.相较于传统的线段树等数据结构,对于含有区间覆盖的操作的问题,珂朵莉树可以更加方便地维护每个被覆盖区间的值.可用于解决区间修改、区间加法、区间查询以及其他区间操作,在处理区间修改效率最高,适用于随机数据。

二:【节点类型】

struct node{
	int l,r,v;//左边界,右边界,颜色 
	const bool operator<(const node b)const{
		return l<b.l;
	}
}; 

三:【节点存储】

我们希望维护所有结点,使得这些结点所代表的区间左端点单调增加且两两不交,最好可以保证所有区间的并是一个极大的连续范围,此处用set维护区间

初始化时,向珂朵莉树中插入一个极长区间

四:【split操作】

split操作是ODT的核心,它接受一个 \(x\) ,把一个包含 \(x\) 的区间 \([l,r]\) 切分为 \([l,x)\)\([x,r]\) ,并返回后者的迭代器

auto split(int x) {
  auto it=odt.lower_bound(Node(x, 0, 0));
  if (it!=odt.end()&&it->l==x) return it;//x=l不需要切分 
  --it;//此处it是[l,r]的迭代器 
  int l=it->l,r=it->r,v=it->v;
  odt.erase(it);
  odt.insert(node(l,x-1,v));
  return odt.insert(node(x,r,v)).first;//insert的返回值是{插入元素的迭代器,插入是否成功} 
}
//返回值也可以是set<node>::iterator

五:【assign操作】

另外一个重要的操作:assign 表示对区间 \([l,r]\) 赋值为 \(v\)

void assign(int l,int r,int v) {
  auto itr=split(r + 1),itl= split(l);
  odt.erase(itl,itr);
  odt.insert(node(l, r, v));
}

六:【perform操作】

将珂朵莉树上的一段区间提取出来并进行操作.与 assign 操作类似,只不过是将删除区间改为遍历区间.

void perform(int l, int r) {
  auto itr=split(r+1),itl=split(l);
  for(;itl!= itr;++itl){
    //...
  }
}

七:【EX】

map维护ODT懒得学,听说更简单??

# 贰:【例题】

一:【P3740 [HAOI2014] 贴海报】

除了ODT,离散化+线段树,并查集模拟链表也可以水过

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
struct node{
	int l,r,v;
	node(int a,int b,int c){
		l=a;r=b;v=c;
	}
	const bool operator<(const node b)const{
		return l<b.l;
	}
};
set<node> odt;
auto sprit(int x){
	auto it=odt.lower_bound(node(x,0,0));
	if(it!=odt.end()&&it->l==x) return it;
	it--;
	int l=it->l,r=it->r,v=it->v;
	odt.erase(it);
	odt.insert(node(l,x-1,v));
	return odt.insert(node(x,r,v)).first;
}
void assign(int l,int r,int v){
	auto itr=sprit(r+1),itl=sprit(l);
	odt.erase(itl,itr);
	odt.insert(node(l,r,v));
}
int main(){
	int n,m;cin>>n>>m;
	odt.insert({1,n,0});
	for(int i=1;i<=m;i++){
		int l,r;cin>>l>>r;
		assign(l,r,i);
	}
	
	set<int> ans;
	for(auto it:odt) ans.insert(it.v);
	cout<<ans.size()-ans.count(0)<<"\n";
	return 0;
}

二:【P1840 Color the Axis】

线段树,分块也可以AC

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
struct node{
	int l,r,v;
	node(int a,int b,int c){
		l=a;r=b;v=c;
	}
	const bool operator<(const node b)const{
		return l<b.l;
	}
};
set<node> odt;
auto sprit(int x){
	auto it=odt.lower_bound(node(x,0,0));
	if(it!=odt.end()&&it->l==x) return it;
	it--;
	int l=it->l,r=it->r,v=it->v;
	odt.erase(it);
	odt.insert(node(l,x-1,v));
	return odt.insert(node(x,r,v)).first;
}
void assign(int l,int r,int v){
	auto itr=sprit(r+1),itl=sprit(l);
	odt.erase(itl,itr);
	odt.insert(node(l,r,v));
}
int perform(int l,int r){
	auto itr=sprit(r+1),itl=sprit(l);
	int res=0;
	while(itl!=itr){
		res+=(itl->r-itl->l+1)*(!itl->v);
		itl++;
	}
	return res;
}
int main(){
	int n,m;cin>>n>>m;
	odt.insert(node(1,n,0));
	int ans=n;
	while(m--){
		int l,r;cin>>l>>r;
		ans-=perform(l,r);
		assign(l,r,1);
		cout<<ans<<"\n"; 
	}
	return 0;
}
posted @ 2026-02-23 16:17  Ming3398  阅读(18)  评论(0)    收藏  举报