概述
又叫老司机树( 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,n−1]分裂下来,就会发现: 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;
}
链接
看这篇博客让我有了写博客的动力。
——因为他写得太好了,让我也想写点什么。
最牛的家伙写的。虽然他有点皮。