转载
作者:互联网
转载自知乎:https://zhuanlan.zhihu.com/p/95931751
珂朵莉树
珂朵莉树(ODT)
- 前言:ODT本质上是一种很暴力的数据结构,就是把一段数列压缩成块以后用\(set\)维护,还是挺\(Naive\)的,关于命名可能是因为提出者是珂学家。
数据结构的定义:
struct ODT
{
ll l,r;
mutable ll w;
ODT(ll L,ll R=-1,ll W=0):l(L),r(R),w(W){}
bool operator < (const ODT &x) const {
return l < x.l;
}
};
每一个结点代表\([l-r]\)的值都是\(w\),然后把所有的结点用\(set\)维护。
例子,如果有一个序列是1 1 1 1 1 2 2 2 2 3 3 3 3,那么我可以用三个结点保存它(1, 5, 1) (6, 9, 2) (10, 14, 3)。
那现在加入我要做区间加法,比如对于\([2-13]\)同时加上\(val\),那就把三个结点分裂成(1, 1, 1) (2, 5, 1) (6, 9, 2) (9, 13, 3) (14, 14, 3)这五个结点,对于成段的结点我暴力单点更新即可。
关键操作
#define IT set<ODT>::iterator
IT split (ll pos) {
IT it = odt.lower_bound(ODT(pos, -1, 0));
if(it != odt.end() && it->l == pos) return it;
it--;
ll L = it -> l, R = it -> r;
ll W = it->w;
odt.erase(it);
odt.insert(ODT(L, pos-1, W));
return odt.insert(ODT(pos, R, W)).first;
}
void assign_val(ll l, ll r, ll val)
{
IT itr = split(r+1),itl = split(l);
odt.erase(itl, itr);
odt.insert(ODT(l, r, val));
}
\(split\)就是区间分裂,\(assignval\)是推平一段区间,即区间赋值。
应用
既然就是暴力,那就能做很多操作了,区间加,区间求和,区间第\(K\)大,区间种类计数都可以使用\(ODT\)完成。
复杂度分析
基本就是玄学,一般用于随机数据的场景。
不过对于一些特殊的题目,比如序列更新几次后就出现大段相同,那就可以直接使用啦。
一道例题
给你一个序列一开始全是1,让你完成三个操作。
区间整体赋0。
区间[l,r]求和以后赋给a[r],然后[l,r-1]置0。
查询[l,r]中有多少个不为0的本质不同的数。
题目还告诉你了询问都是随机数据。
比赛时写了线段树不过不知道为什么没对,大概思路就是前两个操作用线段树维护,询问操作我就每次二分区间,如果区间和为0就不走,否则每次暴力二分区间,走到线段树的叶子上就把数塞进\(set\)。当时没看到题干里是随机数据所以感觉做法挺玄学的,现在想想还挺对:D。
\(ODT\)的代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define rep(i,a,b) for(ll i=a;i<=(ll)b;++i)
#define lowbit(x) ((x)&(-x))
struct ODT
{
ll l,r;
mutable ll w;
ODT(ll L,ll R=-1,ll W=0):l(L),r(R),w(W){}
bool operator < (const ODT &x) const {
return l < x.l;
}
};
set <ODT> odt;
#define IT set<ODT>::iterator
IT split (ll pos) {
IT it = odt.lower_bound(ODT(pos, -1, 0));
if(it != odt.end() && it->l == pos) return it;
it--;
ll L = it -> l, R = it -> r;
ll W = it->w;
odt.erase(it);
odt.insert(ODT(L, pos-1, W));
return odt.insert(ODT(pos, R, W)).first;
}
void assign_val(ll l, ll r, ll val)
{
IT itr = split(r+1),itl = split(l);
odt.erase(itl, itr);
odt.insert(ODT(l, r, val));
}
void modify(ll l,ll r)
{
ll sum=0;
IT itr = split(r+1),itl = split(l);
IT tem=itl;
for(;itl!=itr;++itl) sum+=(itl->w)*(itl->r-itl->l+1);
odt.erase(tem,itr);
if(l != r)
odt.insert(ODT{l, r - 1, 0});
odt.insert(ODT{r, r, sum});
}
ll query(ll l,ll r)
{
set<ll>s;
IT itr = split(r+1),itl = split(l);
for(; itl != itr; ++itl) {
if(itl->w != 0)
s.insert(itl->w);
}
return s.size();
}
int main()
{
ll n, q, op, l, r;
scanf("%lld %lld", &n, &q);
odt.insert(ODT{1, n, 1});
while(q--) {
scanf("%lld %lld %lld", &op, &l, &r);
if(l > r) swap(l, r);
if(op == 1) assign_val(l, r, 1);
else if(op == 2) modify(l, r);
else printf("%lld\n", query(l, r));
}
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
标签:insert,itl,odt,ll,ODT,split,转载 来源: https://www.cnblogs.com/hfyz/p/12003831.html