珂朵莉树及一些相关题目
比分块还要暴力、比线段树还要好写的功能萎缩而强大的数据结构。只要有类似于区间赋值的操作且数据随机,珂朵莉树就可以完成几乎所有查询。
先从一道例题说起:
CF896C
也许是珂朵莉树名字的由来?由于珂朵莉树更加珂学所以人们渐渐放弃了老司机树或者ODT的叫法
区间赋值、数据随机,所以我们就要上乱搞了。很不显然,经过若干次随机操作后,假如把数字相同的一段区间看成一个点的话,原序列只会剩下\(logn\)个点。这就启发我们,就是要把区间缩成一个点。
既然是一个点又要代表一段区间,那结构体就再合适不过了。
同时,我们把这些区间整理在一起,用一个\(set\)维护。
struct node {
int l, r;
mutable ll val; //由于下面区间加的操作要修改val值,所以要加mutable
inline node(int _l, int _r = 0, ll _val = 0) : l(_l), r(_r), val(_val) {}
inline int operator < (const node &a) const {
return l < a.l;
}
};
set <node> s;
珂朵莉树基本操作:
\(split\)
inline set<node>::iterator split(int x)
{
set<node>::iterator it = s.lower_bound(node(x));
if (it != s.end() && it->l == x) return it;
it--;
int l = it->l, r = it->r;
ll val = it->val;
s.erase(it);
s.insert(node(l, x - 1, val));
return s.insert(node(x, r, val)).first; //返回你插入的位置,insert的一个神仙用法
}
将\([l, r]\)的区间分成\([l, x - 1]\)和\([x, r]\),这样\(x\)就成了一个区间的左端点,方便后续处理。
\(assign\)
inline void assign(int l, int r, int x)
{
set<node>::iterator itr = split(r + 1), itl = split(l); //注意这个细节,正着写会RE
s.erase(itl, itr); //删掉一段左闭右开的区间,erase的一个神仙用法
s.insert(node(l, r, x));
}
代码复杂度堪比树状数组……把原来的全删了,再加一个回去,复杂度就是在这里变松了。
其它操作就简单粗暴了……
inline void add(int l, int r, ll x)
{
set<node>::iterator itr = split(r + 1), itl = split(l);
while (itl != itr) itl->val += x, itl++;
}
inline ll get_kth(int l, int r, int k)
{
set<node>::iterator itr = split(r + 1), itl = split(l);
vector<pair<ll, int> > v;
while (itl != itr) v.push_back(make_pair(itl->val, itl->r - itl->l + 1)), itl++;
sort(v.begin(), v.end());
for (vector<pair<ll, int> >::iterator it = v.begin(); it != v.end(); it++) {
if (k <= it->second) return it->first;
k -= it->second;
}
return 0;
}
inline int ksm(int a, int n, int mod)
{
int b = 1;
while (n) {
if (n & 1) b = 1ll * a * b % mod;
a = 1ll * a * a % mod;
n >>= 1;
}
return b;
}
inline int sum(int l, int r, int x, int mod)
{
set<node>::iterator itr = split(r + 1), itl = split(l);
int res = 0;
while (itl != itr) res = (res + 1ll * (itl->r - itl->l + 1) * ksm(itl->val % mod, x, mod)) % mod, itl++;
return res;
}
同样注意\(itr\)要先写就好了。
然后是一些板题(都是线段树题但信奉珂学的话就要用珂朵莉树做)
P2572
一个字,板。
CF915E
注意\(assign\)的同时要处理好答案,如果每次都暴力扫整个\(set\)的话会\(T\)。
P2486
珂朵莉上树
不,是珂朵莉树上树。
查询比较麻烦,要注意有序树剖的各种细节问题,开两个\(vector\),一个存\(x\)到\(lca\)的,另一个存\(y\)到\(lca\)的,然后把第二个\(vector\)倒着接到第一个上面,最后暴力统计答案。
由于在树上依然会经常有很长的链被覆盖成一种颜色,维护一条重链的\(set\)的\(size\)可能会非常小,所以我猜想复杂度会比\(nlog^2n\)要低。
贴下代码吧:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <set>
const int maxn = 1e5 + 7;
using namespace std;
int a[maxn];
int to[maxn << 1];
int nex[maxn << 1];
int last[maxn], k;
int fa[maxn];
int son[maxn];
int sz[maxn];
int dep[maxn];
int top[maxn];
int dfn[maxn], id;
struct node {
int l, r, val;
inline node(int _l, int _r = 0, int _val = 0) : l(_l), r(_r), val(_val) {}
inline int operator < (const node &a) const {
return l < a.l;
}
};
set <node> s;
vector <node> v[2];
inline int read()
{
int X = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') X = X * 10 + ch - '0', ch = getchar();
return X;
}
inline void add_edge(int x, int y)
{
to[++k] = y; nex[k] = last[x]; last[x] = k;
}
inline set<node>::iterator split(int x)
{
set<node>::iterator it = s.lower_bound(node(x));
if (it != s.end() && it->l == x) return it;
it--;
int l = it->l, r = it->r, val = it->val;
s.erase(it);
s.insert(node(l, x - 1, val));
return s.insert(node(x, r, val)).first;
}
inline void assign(int l, int r, int val)
{
set<node>::iterator itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, val));
}
inline void get(int l, int r, int type)
{
set<node>::iterator itr = split(r + 1), itl = split(l);
itr--;
while (1) {
v[type].push_back(*itr);
if (itl == itr) break;
itr--;
}
}
inline int lca(int x, int y)
{
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
inline void upchain(int x, int y, int z)
{
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
assign(dfn[top[x]], dfn[x], z);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
assign(dfn[x], dfn[y], z);
}
inline int sum(int x, int y)
{
v[0].clear();
v[1].clear();
int g = lca(x, y);
while (top[x] != top[g]) {
get(dfn[top[x]], dfn[x], 0);
x = fa[top[x]];
}
get(dfn[g], dfn[x], 0);
while (top[y] != top[g]) {
get(dfn[top[y]], dfn[y], 1);
y = fa[top[y]];
}
get(dfn[g], dfn[y], 1);
for (vector<node>::iterator it = v[1].end() - 1;; it--) {
v[0].push_back(*it);
if (it == v[1].begin()) break;
}
int pre = 0, ans = 0;
for (vector<node>::iterator it = v[0].begin(); it != v[0].end(); it++)
if (it->val != pre) pre = it->val, ans++;
return ans;
}
void dfs1(int x, int f)
{
sz[x] = 1;
fa[x] = f;
dep[x] = dep[f] + 1;
for (int i = last[x]; i; i = nex[i]) {
int y = to[i];
if (y == f) continue;
dfs1(y, x);
sz[x] += sz[y];
if (sz[y] > sz[son[x]]) son[x] = y;
}
}
void dfs2(int x, int f)
{
top[x] = f;
dfn[x] = ++id;
s.insert(node(id, id, a[x]));
if (!son[x]) return;
dfs2(son[x], f);
for (int i = last[x]; i; i = nex[i])
if (to[i] != fa[x] && to[i] != son[x]) dfs2(to[i], to[i]);
}
int main(void)
{
int n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i < n; i++) {
int x = read(), y = read();
add_edge(x, y);
add_edge(y, x);
}
dfs1(1, 0);
dfs2(1, 1);
while (m--) {
char opt;
scanf(" %c", &opt);
int x = read(), y = read();
if (opt == 'C') upchain(x, y, read());
else printf("%d\n", sum(x, y));
}
return 0;
}