珂朵莉树

概念

珂朵莉树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 1in,将区间 [ 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 1n,m105

操作上面都讲了。

 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));
	}
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值