珂朵莉树

珂朵莉树

BY OI WIKI:

这个名称指代的是一种「使用平衡树(std::set、std::map 等)或链表(std::list、手写链表等)维护颜色段均摊」的技巧,而不是一种特定的数据结构.其核心思想是将值相同的一段区间合并成一个结点处理.相较于传统的线段树等数据结构,对于含有区间覆盖的操作的问题,珂朵莉树可以更加方便地维护每个被覆盖区间的值.

也就是说,区间推平、数据随机的问题是珂朵莉树喜欢的。可能除了推平以外,还夹杂其他操作。这里记录一下这个东西的原理:

原理

这个东西比较常见的维护是用 set 维护连续段。具体的用 set 维护段 \([l,r,v]\),表示区间 \([l,r]\) 的值为 \(v\)

#define iter set <node> iterator
struct node
{
    ll l, r; 
    mutable ll v; 
    node(ll l, ll r = 0, ll v = 0) : l(l), r(r), v(v) {}
    bool operator < (const node &a) const { return l < a.l; }
} ;

分裂(split)

给定参数 \(p\),把区间 \([l,r]\) 分裂成 \([l,p)\)\([p,r]\)。返回的是 \([p,r]\) 的迭代器。

然后过程就是我们找第一个左端点 \(\ge p\) 的区间迭代器 \(it\)。如果 \(p\) 是某个区间开头,直接返回这个区间对应的迭代器。如果找不到就退出。那么 --it 就是 \(p\) 对应的区间。那么直接 erase 掉即可。

iter split(ll p)
{
	iter it = s.lower_bound(node(p)); 
	if (it != s.end() && it.l == p) return it; 
	-- it; 
	if (it.r < p) return s.end(); 
	ll l = it.l; 
	ll r = it.r; 
	ll v = it.v; 
	s.erase(it); 
	s.insert(node(l, p - 1, v)); 
	return s.insert(node(p, r, v)).first; 
}

推平(assign)

把区间 \([l,r]\) 赋值为 \(v\)

void assign(ll l, ll r, ll v) 
{
    iter itr = split(r + 1), itl = split(l);
    s.erase(itl, itr);
    s.insert(node(l, r, v));
}

修改

对区间 \([l,r]\) 进行修改操作。以区间加 \(x\) 为例子:

void add(ll l, ll r, ll x)
{
    iter itr = split(r + 1), itl = split(l);
	for (iter it = itl; it != itr; ++ it ) 
		it.v += x; 
}

其他修改操作同理。

例题

CF896C

板子。

struct node 
{
	ll l, r; 
	mutable ll v; 
	node(ll l, ll r = 0, ll v = 0) : l(l), r(r), v(v) {} 
	bool operator < (const node &t) const { return l < t.l; }
} ; 
#define iter set <node>::iterator 
set <node> s; 

bool pp(node x, node y) { return x.v < y.v; }
ll fstpow(ll x, ll n, ll mod)
{
	ll s = 1; x %= mod; 
	while (n)
	{
		if (n & 1) s = s * x % mod; 
		n >>= 1; 
		x = x * x % mod; 
	}
	return s; 
}

iter split(ll p)
{
	// [l, r] -> [l, p) + [p, r]
	iter it = s.lower_bound(node(p)); 
	if (it != s.end() && it -> l == p) return it; 
	-- it; 
	if (it -> r < p) return s.end(); 
	ll l = it -> l; 
	ll r = it -> r; 
	ll v = it -> v; 
	s.erase(it); 
	s.insert(node(l, p - 1, v)); 
	return s.insert(node(p, r, v)).first; 
}
void assign(ll l, ll r, ll v)
{
	iter itr = split(r + 1), itl = split(l); 
	s.erase(itl, itr); 
	s.insert(node(l, r, v)); 
}
void add(ll l, ll r, ll x)
{
	iter itr = split(r + 1), itl = split(l); 
	for (auto it = itl; it != itr; ++ it ) 
		it -> v += x; 
}
ll kth(ll l, ll r, ll k)
{
	vector <node> vec; 
	iter itr = split(r + 1), itl = split(l); 
	for (auto it = itl; it != itr; ++ it ) 
		vec.pb(node(it -> l, it -> r, it -> v)); 
	sort(vec.begin(), vec.end(), pp); 
	for (auto x : vec)
	{
		if (k > x.r - x.l + 1)
			k -= (x.r - x.l + 1);
		else 
			return x.v; 
	}
	return 114514; 
}
ll getS(ll l, ll r, ll x, ll y)
{
	iter itr = split(r + 1), itl = split(l); 
	ll Res = 0; 
	for (auto it = itl; it != itr; ++ it ) 
	{
		ll d = fstpow(it -> v, x, y); 
		d = d * (it -> r - it -> l + 1) % y; 
		Res = (Res + d) % y; 
	}
	return Res; 
}

P1840

仍然是板子,这个用 sgt 和分块也可以做。这里记录一下简单做法:

\(f_i\) 表示 \(i\) 较远的一个被染白的点,满足 \([f_i,i]\) 都被染白。初始 \(f_i = i\)。每次对 \([l,r]\) 染色的时候,从右往左扫,如果 \(i\) 是白色的就跳到 \(f_i\),这个过程可以用并查集优化。如果 \(i\) 是黑色的,就令 \(f_i \leftarrow l\)\(i \leftarrow i-1\) 即可。这样就在几乎线性的时间复杂度解决了问题。

ll Q(ll l, ll r)
{
	iter itr = split(r + 1), itl = split(l); 
	ll Res = 0; 
	for (auto it = itl; it != itr; ++ it )
		Res += (it -> v == 1); 
	return Res; 
}

CF915E

同样是板子。不过这个题要注意优化,我们在 assign 的时候直接计算总和,这样跑的很快。

void assign(ll l, ll r, ll v)
{
	iter itr = split(r + 1), itl = split(l); 
	for (auto it = itl; it != itr; ++ it ) Res -= (it -> r - it -> l + 1) * (it -> v); 
	s.erase(itl, itr); 
	s.insert(node(l, r, v)); 
	Res += (r - l + 1) * v; 
}

P4979

究极 sb 题。这个题直接朴素地写 odt 会 tle。你需要直接维护值相同的尽量长的段,这个在 assign 的时候可以暴力维护。细节很多,很难写。

void assign(ll l, ll r, ll v)
{
	iter itr = split(r + 1), itl = split(l); 
	itl -> v = v; 
	while (itl != s.begin() && (-- itl) -> v == v) ; 
	if (itl -> v != v) ++ itl; 
	
	-- itr; itr -> v = v; 
	auto rend = s.end(); -- rend; 
	while (itr != rend && (++ itr) -> v == v) ; 
	if (itr -> v != v) -- itr; 
	
	l = itl -> l; 
	r = itr -> r; ++ itr; 
	s.erase(itl, itr); 
	s.insert(node(l, r, v));  
}
 
bool check(ll l, ll r)
{
//	for (auto t : s) cout << t.l << ' ' << t.r << ' ' << t.v << '\n';  
	 
	iter itl = s.upper_bound(node(l)); 
	iter itr = s.upper_bound(node(r)); 
	
	-- itl; -- itr; 
	
	if (itl != itr) return false; 
	if (l == 1 || r == n) return true; 
	
	if (itl -> l == l) -- itl; 
	if (itr -> r == r) ++ itr; 
	return (itl -> v != itr -> v); 
}
posted @ 2026-06-13 22:45  RainyRadio  阅读(2)  评论(0)    收藏  举报