参考资料:
珂朵莉树的定义
珂朵莉树适用于区间操作且数据随机的题目。
珂朵莉树结点的定义如下:
#define set<NODE>::iterator IT
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& a)const{return l<a.l;}
}
珂朵莉树定义如下:
set<NODE> tree;
分裂操作
功能:将 <l, r, v>
分裂成 <l, pos-1, v>
和 <pos, r, v>
,返回后一个区间的迭代器。
IT split(int pos){
IT it = tree.lower_bound(NODE(pos, 0, 0));
if(it != tree.end() && it->l == pos){
return it;
}
it--;
int l = it->l, r = it->r, v = it->v;
tree.erase(it);
tree.insert(NODE(l, pos-1, v));
return tree.insert(NODE(pos, r, v)).first;
}
区间操作
这是珂朵莉树的精髓。
假设我们要对区间 [l ,r]
进行某种操作:
void modify(int l, int r, int v){
// 顺序不能颠倒
IT ed = split(r+1), st = split(l);
// 对涉及到区间逐一操作
for(IT it=st;it!=ed;it++){
...
}
// 如果是类似区间赋值的操作,还可以把涉及到的所有区间合并
tree.erase(st, ed);
tree.insert(NODE(l, r, v));1
}
例1 CF915E Physical Education Lessons
题意:
Alex高中毕业了,他现在是大学新生。虽然他学习编程,但他还是要上体育课,这对他来说完全是一个意外。快要期末了,但是不幸的Alex的体育学分还是零蛋!
Alex可不希望被开除,他想知道到期末还有多少天的工作日,这样他就能在这些日子里修体育学分。但是在这里计算工作日可不是件容易的事情:
从现在到学期结束还有 n n n 天(从 1 1 1 到 n n n 编号),他们一开始都是工作日。接下来学校的工作人员会依次发出 q q q 个指令,每个指令可以用三个参数 l , r , k l,r,k l,r,k 描述:
-
如果 k = 1 k=1 k=1,那么从 l l l 到 r r r (包含端点)的所有日子都变成非工作日。
-
如果 k = 2 k=2 k=2,那么从 l l l 到 r r r (包含端点)的所有日子都变成工作日。
帮助Alex统计每个指令下发后,剩余的工作日天数。
输入格式:
第一行一个整数 n n n,第二行一个整数 q q q ( 1 ≤ n ≤ 1 0 9 , 1 ≤ q ≤ 3 ⋅ 1 0 5 ) (1\le n\le 10^9,\;1\le q\le 3\cdot 10^5) (1≤n≤109,1≤q≤3⋅105),分别是剩余的天数和指令的个数。
接下来 q q q 行,第 i i i 行有 3 3 3 个整数 l i , r i , k i l_i,r_i,k_i li,ri,ki,描述第 i i i 个指令 ( 1 ≤ l i , r i ≤ n , 1 ≤ k ≤ 2 ) (1\le l_i,r_i\le n,\;1\le k\le 2) (1≤li,ri≤n,1≤k≤2)。
输出格式:
输出 q q q 行,第 i i i 行表示第 i i i 个指令被下发后剩余的工作日天数。
样例 #1
样例输入 #1
4
6
1 2 1
3 4 1
2 3 2
1 3 2
2 4 1
1 4 2
样例输出 #1
2
0
2
3
1
4
思路
珂朵莉树实现区间赋值,并在赋值过程中维护区间和。
代码
#include<bits/stdc++.h>
using namespace std;
struct NODE{
int l, r;
mutable int v;
NODE(int l=0, int r=0, int v=0):l(l), r(r), v(v){}
bool operator<(const NODE& a)const{
return l<a.l;
}
};
set<NODE> tree;
int n, q;
int ans = 0;
set<NODE>::iterator split(int pos){
set<NODE>::iterator it = tree.lower_bound(NODE(pos, 0, 0));
if(it != tree.end() && it->l == pos){
return it;
}
it--;
int l = it->l, r = it->r, v = it->v;
tree.erase(it);
tree.insert(NODE(l, pos-1, v));
return tree.insert(NODE(pos, r, v)).first;
}
void assign(int l, int r, int v){
set<NODE>::iterator ed = split(r+1), st = split(l);
for(set<NODE>::iterator it=st;it!=ed;it++){
ans -= it->v*(it->r-it->l+1);
}
tree.erase(st, ed);
tree.insert(NODE(l, r, v));
ans += v*(r-l+1);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n>>q;
tree.insert(NODE(1, n, 1));
ans = n;
for(int i=1;i<=q;i++){
int l, r, v;
cin>>l>>r>>v;
assign(l, r, v-1);
cout<<ans<<'\n';
}
return 0;
}
总结
在数据随机的前提下,珂朵莉树的时间复杂度为 O ( n log n log n ) O(n\log n\log n) O(nlognlogn)。然而很多题目会特意卡珂朵莉树,所以珂朵莉树往往只能拿部分分。