hello_world_djh

orz

关注我

珂朵莉树

定义

什么是珂朵莉树

珂朵莉
这就是珂朵莉树↑
一种暴力神奇数据结构,又名老司机树(old driver tree,ODT)。支持区间推平,区间修改,区间查询。
珂朵莉树这么珂爱,为什么不学呢?

适用题型

所有以区间推平为主要操作的题目,珂朵莉树的乱搞做法主要靠区间推平维持时间复杂度的。本博客的代码默认以SP13015为例。(因为这道题翻译是我翻译的

实现

建立

建立一个 set,维护一些节点,每个节点维护一个区间。
具体实现就是每个节点维护l,r,val。表示[l,r]这个区间所有数都等于val
因为我们要存到 set 中,所以要重载小于号,这里我们以左端点排序,在 split 会讲原因。
最好定义一个重载函数,在 split 会讲原因。
直接上代码吧:

点击查看代码
struct ODT {
	int l,r;
	mutable int val;
	bool operator < (const ODT &x) {
		return l < x.l;
	}
	ODT(int _l = 0; int _r = 0; int _val = 0):l(_l), r(_r), val(_val) {return;}
}
set<ODT> tree;

小贴士: mutable 指可以在 set 里修改的变量,虽然此题不用加,但是大部分题目有对 val 的修改,所以建议加上。如CF896C
接下来讲两个重要函数:

split

split(x):我们要返回一个指针,指针指向以 \(x\) 为左端点的区间。
实现也非常简单。如果有这个区间,直接返回。但如果这个区间不存在,我们要将包含 \(x\) 的区间分裂成以 \(x - 1\) 为右端点的一个区间和以 \(x\) 为左端点的区间,并返回它的指针。
因为我们前面定义部分以左端点大小重载了小于运算符,所以我们可以用到二分查找函数 lower_bound 查找。因为我们有重载函数,所以可以调用直接生成一个节点,该节点的 rval 都是默认值。
直接上代码:

点击查看代码
It split(int x) {
	It it = lower_bound(ODT(x));//查找
	if (it != tree.end() && it->l == x) return it;//找到直接返回
	it--;//因为lower_bound是查找大于等于该节点的第一个节点,但我们需要小于该节点的第一个节点,所以应该向左移动
	int l = it->l, r = it->r, val = it->val;//先存一下,待会会删除该指针
	tree.erase(it); tree.insert(ODT(l, x - 1, val));//删除和插入
	return tree.insert(ODT(x, r, val)).first;//insert 返回一个二元组,第一个是指针,第二个是该节点的值
}

assign

assign(l,r,val):大家喜闻乐见的区间推平。解释一下开头说的 assign 维护时间复杂度的原理。
原理很简单,因为 ODT 对于每个节点是暴力维护的,跟分块思路差不多。所以节点个数越小时间复杂度越优秀。
而区间推平就可以减少多余的节点个数,使得节点数目减少优化复杂度。
举个直观点的例子就是如果只有一个节点单词修改的复杂度是 \(0(1)\) 的,但是如果有 \(n\) 个节点的话单次修改的复杂度就是 \(0(n)\),与朴素做法同阶。
上代码:

点击查看代码
void assign(int l, int r, int val) {
	It itr = split(r + 1), itl = split(l);
	tree.erase(itl, itr);
	tree.insert(ODT(l, r, val));
	return;
}

小贴士:seterase 操作既支持删除一个指针指着的位置,也支持删除一段节点。但删除的区间是左闭右开的区间,所以要以 r + 1 为左端点。记得要先分裂 r + 1 再分裂 l。因为如果先分裂 l,分裂 r + 1 时就可能是之前 l 分裂的指针失效。比如 split(l) 分裂的区间为 [l, r + 2], 再分裂 r + 1 时就会覆盖之前的操作。

具体题目

你已经学会a+b了现在可以做微积分了
简单题目:SP13015 CNTPRIME - Counting Primes
要求支持区间推平,区间查询素数个数。欧拉筛 \(O(n)\) 预处理素数表,剩下的暴力ODT操作即可。
个人认为埃氏筛也行
上代码:

点击查看代码
#include <bits/stdc++.h>
#define It set<ODT>::iterator

using namespace std;

const int N = 2e6 + 10;
int prime[N], pcnt; bool not_prime[N];

void eular() {
	not_prime[0] = not_prime[1] = true;
	for (int i = 2; i <= N; i++) {
		if (!not_prime[i]) {
			prime[++pcnt] = i;
		}
		for (int j = 1; j <= pcnt && i * prime[j] <= N; j++) {
			not_prime[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
		}
	}
	return;
}

struct ODT {
	int l, r;
	mutable int val;
	bool operator < (const ODT &x) const {
		return l < x.l;
	}
	ODT(int _l = 0, int _r = 0, int _val = 0):l(_l), r(_r), val(_val){return;}
};
set<ODT> tree;

It split(int x) {
	It it = tree.lower_bound(ODT(x));
	if (it != tree.end() && it->l == x) return it;
	it--; int l = it->l, r = it->r, val = it->val;
	tree.erase(it); tree.insert(ODT(l, x - 1, val));
	return tree.insert(ODT(x, r, val)).first;
}

void assign(int l, int r, int val) {
	It itr = split(r + 1), itl = split(l);
	tree.erase(itl, itr);
	tree.insert(ODT(l, r, val));
	return;
}

int query(int l, int r) {
	int ans = 0; It itr = split(r + 1), itl = split(l);
	for (It it = itl; it != itr; it++) {
		if (!not_prime[it->val]) ans += it->r - it->l + 1;
	}
	return ans;
}

int t, n, q;

int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	eular();
	cin >> t;
	for (int i = 1; i <= t; i++) {
		cout << "Case " << i << ':' << endl;
		cin >> n >> q;
		tree.clear();
		for (int j = 1, x; j <= n; j++)
			cin >> x, tree.insert(ODT(j, j, x));
		for (int j = 1, op, x, y, v; j <= q; j++) {
			cin >> op >> x >> y;
			if (!op) {
				cin >> v;
				assign(x, y, v);
			}
			else {
				cout << query(x, y) << endl;
			}
		}
	}
	return 0;
}

也很简单的题:CF915E Physical Education Lessons
纯区间推平。ODT轻松切,也没什么好讲的。
上代码:

点击查看代码
#include<bits/stdc++.h>
#define It set<ODT>::iterator
#define ll long long
using namespace std;
ll ans;
struct ODT{
	ll l,r;
	bool val;
	ODT(){return;}
	ODT(ll _l=0){l=_l;return;}
	ODT(ll _l,ll _r,bool _val):l(_l),r(_r),val(_val){return;}
	bool operator < (const ODT &x)const
	{
		return l<x.l;
	}
};
set<ODT> tree;
It split(ll x)
{
	It it=tree.lower_bound(ODT(x));
	if(it!=tree.end() && it->l==x)
		return it;
	it--;
	ll l=it->l,r=it->r;bool val=it->val;
	tree.erase(it);
	tree.insert(ODT(l,x-1,val));
	return tree.insert(ODT(x,r,val)).first;
}
void assign(ll l,ll r,bool val)
{
	It itr=split(r+1),itl=split(l),it=itl;
	for(;it!=itr;it++)
		ans-=it->val*(it->r-it->l+1);
	tree.erase(itl,itr);
	tree.insert(ODT(l,r,val));
	ans+=(r-l+1)*val;
	return;
}
int n,q;
int main()
{
	scanf("%d%d",&n,&q);
	tree.insert(ODT(1,n,true));
	ans=n;
	for(int i=1;i<=q;i++)
	{
		int op;
		ll l,r;
		scanf("%lld%lld%d",&l,&r,&op);
		switch(op)
		{
			case 1:{
				assign(l,r,false);
				break;
			}
			default:{
				assign(l,r,true);
				break;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2022-07-26 10:21  hello_world_djh  阅读(81)  评论(0)    收藏  举报