算法自学__珂朵莉树

参考资料:

珂朵莉树的定义

珂朵莉树适用于区间操作数据随机的题目。

珂朵莉树结点的定义如下:

#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) (1n109,1q3105),分别是剩余的天数和指令的个数。

接下来 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) (1li,rin,1k2)

输出格式:

输出 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)。然而很多题目会特意卡珂朵莉树,所以珂朵莉树往往只能拿部分分。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值