概念
珂朵莉树(ODT
),适用于有区间赋值操作且数据随机的题目。
珂朵莉树是一种“基于数据随机的颜色段均摊”,通过 set
维护区间。
其复杂度依赖于 assign
操作。
因此,题目中必须含有区间赋值。
同时,珂朵莉树依赖于数据随机。
在随机数据下, 使用 set
实现的珂朵莉树可以达到
O
(
n
log
log
n
)
\mathcal O(n\log \log n)
O(nloglogn) 的复杂度(参见 这篇文章)。
思想
珂朵莉树的思想在于随机数据下的区间赋值操作很可能让大量元素变为同一个数。所以我们以三元组 ( l , r , v ) (l,r,v) (l,r,v)的形式保存数据(区间 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fRkOMxtF-1644631200824)(https://www.zhihu.com/equation?tex=%5Bl%2Cr%5D)] 中的元素的值都是 v v v):
struct node
{
ll l, r;
mutable ll v;
node(ll l, ll r, ll v) : l(l), r(r), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
然后把这些三元组存储到 set
里(也可以用链表,但 set
更优秀):
set<node> odt;
实现
build \text{build} build
对于
∀
1
≤
i
≤
n
\forall1 \leq i\leq n
∀1≤i≤n,将区间
[
i
,
i
]
[i,i]
[i,i] 插入 set
即可。
for (int i = 1; i <= n; i++)
{
odt.insert(node(i, i, a[i]));
}
split \text{split} split
split
是最核心的操作之一,它用于将原本包含点
x
x
x 的区间(设为
[
l
,
r
]
[l,r]
[l,r])分裂为两个区间
[
l
,
x
)
[l,x)
[l,x) 和
[
x
,
r
]
[x,r]
[x,r] 并返回指向后者的迭代器。
auto split(int x)
{
if (x > n)
return odt.end();
auto it = --odt.upper_bound((node){x, 0, 0});
if (it->l == x)
return 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;
}
assign \text{assign} assign
另外一个重要的操作 assign
用于对一段区间进行赋值。
对于 ODT
来说,区间操作只有这个比较特殊,也是保证复杂度的关键。
如果 ODT
里全是长度为
1
1
1 的区间,就成了暴力,但是有了 assign
,可以使 ODT
的复杂度下降。
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));
}
其他操作
套个模板就好了。
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));
}
区间加:
void add(ll l, ll r, ll v)
{
auto end = split(r + 1);
for (auto it = split(l); it != end; it++)
it->v += v;
}
区间第 k k k 小:
ll kth(ll l, ll r, ll k)
{
auto end = split(r + 1);
vector<pair<ll, ll>> v;
for (auto it = split(l); it != end; it++)
v.push_back(make_pair(it->v, it->r - it->l + 1));
sort(v.begin(), v.end());
for (int i = 0; i < v.size(); i++)
{
k -= v[i].second;
if (k <= 0)
return v[i].first;
}
}
区间 x x x 次方和:
ll sum_of_pow(ll l, ll r, ll x, ll y)
{
ll tot = 0;
auto end = split(r + 1);
for (auto it = split(l); it != end; it++)
tot = (tot + qpow(it->v, x, y) * (it->r - it->l + 1)) % y;
return tot;
}
例题
CF896C \text{CF896C} CF896C
请你写一种奇怪的数据结构,支持:
1 l r x
:将 [ l , r ] [l,r] [l,r] 区间所有数加上 x x x;2 l r x
:将 [ l , r ] [l,r] [l,r] 区间所有数改成 x x x;3 l r x
:输出 [ l , r ] [l,r] [l,r] 区间第 x x x 小值;4 l r x y
输出 [ l , r ] [l,r] [l,r] 区间所有数的 x x x 次方的和 m o d y \bmod y mody 的值。
1 ≤ n , m ≤ 1 0 5 1 \leq n,m\leq 10^5 1≤n,m≤105。
操作上面都讲了。
4.34s / 9.96MB / 2.38KB C++20 O2 \text{ 4.34s / 9.96MB / 2.38KB C++20 O2} 4.34s / 9.96MB / 2.38KB C++20 O2。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read()
{
ll x = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
{
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
struct node
{
ll l, r;
mutable ll v;
node(ll l, ll r, ll v) : l(l), r(r), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
set<node> odt;
auto split(ll pos)
{
auto it = odt.lower_bound(node(pos, 0, 0));
if (it != odt.end() && it->l == pos)
return it;
it--;
ll l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(node(l, pos - 1, v));
return odt.insert(node(pos, r, v)).first;
}
void assign(ll l, ll r, ll v)
{
auto end = split(r + 1), begin = split(l);
odt.erase(begin, end);
odt.insert(node(l, r, v));
}
ll qpow(ll a, ll n, ll p)
{
ll ans = 1;
a %= p;
while (n)
{
if (n & 1)
ans = ans * a % p;
n >>= 1;
a = a * a % p;
}
return ans;
}
ll n, m, seed, vmax;
ll rnd()
{
ll ret = seed;
seed = (seed * 7 + 13) % 1000000007;
return ret;
}
void add(ll l, ll r, ll v)
{
auto end = split(r + 1);
for (auto it = split(l); it != end; it++)
it->v += v;
}
ll kth(ll l, ll r, ll k)
{
auto end = split(r + 1);
vector<pair<ll, ll>> v;
for (auto it = split(l); it != end; it++)
v.push_back(make_pair(it->v, it->r - it->l + 1));
sort(v.begin(), v.end());
for (int i = 0; i < v.size(); i++)
{
k -= v[i].second;
if (k <= 0)
return v[i].first;
}
}
ll sum_of_pow(ll l, ll r, ll x, ll y)
{
ll tot = 0;
auto end = split(r + 1);
for (auto it = split(l); it != end; it++)
tot = (tot + qpow(it->v, x, y) * (it->r - it->l + 1)) % y;
return tot;
}
signed main()
{
n = read(), m = read(), seed = read(), vmax = read();
for (int i = 1; i <= n; ++i)
{
int r = rnd();
odt.insert(node(i, i, r % vmax + 1));
}
for (int i = 1; i <= m; ++i)
{
ll opr = rnd() % 4 + 1, l = rnd() % n + 1, r = rnd() % n + 1, x, y;
if (l > r)
swap(l, r);
if (opr == 3)
x = rnd() % (r - l + 1) + 1;
else
x = rnd() % vmax + 1;
if (opr == 4)
y = rnd() % vmax + 1;
switch (opr)
{
case 1:
add(l, r, x);
break;
case 2:
assign(l, r, x);
break;
case 3:
printf("%lld\n", kth(l, r, x));
break;
case 4:
printf("%lld\n", sum_of_pow(l, r, x, y));
}
}
return 0;
}
luogu P4344 \text{luogu P4344} luogu P4344
题意自行去看。
暴力珂朵莉树即可。
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int x = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
{
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
int n, q;
struct node
{
int l, r;
mutable int v;
node(int l, int r, int v) : l(l), r(r), v(v) {}
bool operator<(const node &o) const { return l < o.l; }
};
set<node> odt;
auto split(int pos)
{
auto it = odt.lower_bound(node(pos, 0, 0));
if (it != odt.end() && it->l == pos)
return it;
it--;
int l = it->l, r = it->r, v = it->v;
odt.erase(it);
odt.insert(node(l, pos - 1, v));
return odt.insert(node(pos, r, v)).first;
}
void assign(int l, int r, int v)
{
auto end = split(r + 1), begin = split(l);
odt.erase(begin, end);
odt.insert(node(l, r, v));
}
void modify(int x, int y, int l, int r)
{
auto end = split(y + 1), begin = split(x);
int sum = 0;
for (auto it = begin; it != end; it++)
if (it->v)
sum += it->r - it->l + 1;
assign(x, y, 0);
end = split(r + 1), begin = split(l);
for (auto it = begin; it != end && sum; it++)
if (!(it->v))
{
if (sum >= (it->r - it->l + 1))
{
it->v = 1;
sum -= (it->r - it->l + 1);
}
else
{
assign(it->l, it->l + sum - 1, 1);
sum = 0;
}
}
}
int query(int l, int r)
{
auto end = split(r + 1), begin = split(l);
int res = 0, ans = 0;
for (auto it = begin; it != end; it++)
if (!(it->v))
res += it->r - it->l + 1;
else
{
ans = max(ans, res);
res = 0;
}
return max(ans, res);
}
signed main()
{
n = read(), q = read();
odt.insert(node(1, n, 1));
int opt, x, y, l, r;
while (q--)
{
opt = read(), x = read(), y = read();
if (!opt)
{
assign(x, y, 0);
}
else if (opt == 1)
{
l = read(), r = read();
modify(x, y, l, r);
}
else
printf("%d\n", query(x, y));
}
}