珂朵莉树
定义
什么是珂朵莉树
这就是珂朵莉树↑
一种暴力神奇数据结构,又名老司机树(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
查找。因为我们有重载函数,所以可以调用直接生成一个节点,该节点的 r
和 val
都是默认值。
直接上代码:
点击查看代码
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;
}
小贴士:set
的 erase
操作既支持删除一个指针指着的位置,也支持删除一段节点。但删除的区间是左闭右开的区间,所以要以 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;
}