其他分享
首页 > 其他分享> > 转载

转载

作者:互联网

转载自知乎: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,让你完成三个操作。

  1. 区间整体赋0。

  2. 区间[l,r]求和以后赋给a[r],然后[l,r-1]置0。

  3. 查询[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