珂朵莉树
珂朵莉树
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);
}
浙公网安备 33010602011771号