【学习笔记】珂朵莉树

概述

又叫老司机树( Old Driver Tree, ODT \text{Old Driver Tree, ODT} Old Driver Tree, ODT)一种 暴力 的数据结构,用于处理一些区间查询的问题。

思想

在一些问题中,满足某段区间的值都是一样的,所以可以用一个东西去维护它。一般使用 set \text{set} set

题外话:那么为什么叫做“树”呢?就因为是红黑树( set \text{set} set)吗?

什么时候用

(往往是)使一整段区间内的东西变得一样的修改;在某一段这样的区间内查询很快,数据随机

因为这样的修改才便于 推平。推平是时间复杂度的保障。——什么是推平?往下看。

实现

0x01定义
定义 node \text{node} node类型,用于存储某个区间的信息。一般长这样:

struct Node{
	int l, r; // 区间左右端点 
	... // 维护的东西 
	bool operator<(const Node &that)const{
		return l < that.l;
	}
};
set<Node> s;

然后就搞定了!

0x02初始化
一般是这样的:

	s.insert(Node(1,n,...)); // 初始信息 

0x03基本操作:分裂
发现在初始化里,我们直接塞了一个 [ 1 , n ] [1,n] [1,n],但是查询(修改)不是 [ 1 , n ] [1,n] [1,n]怎么办?

[ 1 , n ] [1,n] [1,n] 分裂 成两个区间!一般长这样:

set<Node>::iterator split(int x){
	auto it = s.lower_bound(Node(x));
	if(it != s.end() and it->l == x)
		return it;
	-- it;
	int L = it->l, R = it->r, ...; // 存储信息 
	s.erase(it);
	s.insert(Node(L,x-1,...));
	return s.insert(Node(x,R,...)).first;
}

小知识: set::insert \text{set::insert} set::insert返回 pair<iterator,bool> \text{pair<iterator,bool>} pair<iterator,bool>,分别为新加入元素迭代器、是否加入成功。

0x04基本操作:推平
如果一直 split \text{split} split,岂不是比暴力还要慢(多一个 log ⁡ \log log)?

好方法就是,将信息相同的区间重新 合并 为大区间!一般长这样:

void mergeNode(int l,int r){
	auto R = split(r+1), L = split(l);
	s.erase(L,R);
	s.insert(...); // 中间的区间的信息
}

注意!要先分裂 r r r,再分裂 l l l。否则——你只需要带入 s = { [ 1 , n ] } s=\{[1,n]\} s={[1,n]},试着把 [ 2 , n − 1 ] [2,n-1] [2,n1]分裂下来,就会发现: split ( l ) \text{split}(l) split(l)传回来的迭代器指向的元素在 split ⁡ ( r ) \operatorname{split}(r) split(r)中被删掉了!

小知识: set::erase(iterator,iterator) \text{set::erase(iterator,iterator)} set::erase(iterator,iterator)可以左闭右开的删除元素。

0x05进阶操作:查询
说了是暴力的数据结构!直接在 set \text{set} set里暴力找范围内的区间。

例题

我不会做。主要是因为我只会拿这玩意儿来 骗分 做奇怪的题。

0x01下雨天
反正这道题 nowcoder1105B \text{nowcoder1105B} nowcoder1105B)我就是写的 ODT \text{ODT} ODT。能过 85pts \text{85pts} 85pts(但是我写炸了……而且忘了有大样例……只得了 20pts \text{20pts} 20pts……暴力模拟都有 30pts \text{30pts} 30pts……)。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -1;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x%10)+'0');
}

int n, q;
 
void init(){
	n = readint(), q = readint();
}

// Chtholly Tree
struct Node{
	int l, r, v;
	Node(int X):l(X){ r = -1; }
	Node(int L,int R,int V):l(L),r(R),v(V){}
	bool operator<(const Node &that)const{
		if(l != that.l)
			return l < that.l;
		return r < that.r;
	}
};
set<Node> s;
set<Node>::iterator split(int x){
	auto it = s.lower_bound(Node(x));
	if(it != s.end() and (it->l) == x)
		return it;
	-- it;
	int l = (it->l), r = (it->r), v = (it->v);
	s.erase(it);
	s.insert(Node(l,x-1,v));
	return s.insert(Node(x,r,v)).first;
}
long long getSum(int first,int last){ // 等差数列求和
	return (long long)(first+last)*(last-first+1)>>1;
}
long long ans;
void drop(int x){
	auto it = s.begin();
	for(int len; it!=s.end(); ++it){
		if(not (it->v)) continue;
		len = (it->r)-(it->l)+1;
		if(x <= len) break;
		x -= len;
		ans -= getSum(n+1-(it->r),n+1-(it->l));
	}
	if(it == s.end()){
		s.clear(); s.insert(Node(1,n,0)); return ;
	}// 我之前直接return了……大样例输出为负数…… 
	int r = (it->l)+x-1;
	ans -= getSum(n+1-r,n+1-(it->l));
	it = split(r+1);
	if(it->v == 0) r = it->r, ++ it;
	s.erase(s.begin(),it);
	s.insert(Node(1,r,0));
}
void rise(int x){
	auto it = s.begin();
	for(int len; it!=s.end(); ++it){
		if(it->v) continue;
		len = (it->r)-(it->l)+1;
		if(x <= len) break;
		x -= len;
		ans += getSum(n+1-(it->r),n+1-(it->l));
	}
	if(it == s.end()){
		s.clear(); s.insert(Node(1,n,1)); return ;
	}
	int r = (it->l)+x-1;
	ans += getSum(n+1-r,n+1-(it->l));
	it = split(r+1);
	if(it->v == 1) r = it->r, ++ it;
	s.erase(s.begin(),it);
	s.insert(Node(1,r,1));
}

void solve(){
	s.insert(Node(1,n,0));
	while(q --){
		int delta = readint();
		if(delta > 0) rise(delta);
		else drop(-delta);
		writeint(ans);
		putchar('\n');
	}
}
 
int main(){
	init();
	solve();
	return 0;
}

链接

这篇博客让我有了写博客的动力。

——因为他写得太好了,让我也想写点什么。

最牛的家伙写的。虽然他有点皮。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值