珂朵莉树学习笔记
一个神奇的数据结构 , 当数据随机情况下可以乱杀
又名珂朵莉树
基本概念与条件性
利用set维护序列的数据结构 , 将原序列分为了若干块 , 用三元组(l,r,sum)表示
例如:2 3 3 3 4 2 7 7 8
\((1,1,2) , (2,4,3) , (5,5,4) , (6,6,2) , (7,8,7) , (9,9,8)\)
显然这个数据结构是一种基于暴力的数据结构,非常不稳定
若使用 \(ODT\) 做题,则该题目必须保证
\(1\).数据足够弱
\(2\).有区间推平操作
这样才能保证其复杂度
初始化函数
struct node{
int l,r;
mutable int sum; // mutable可以让set中的sum成为可变元素!!切记
friend bool operator <(node A,node B)
{
return A.l < B.l;
}
};
set<node> s;
珂朵莉树有两个核心函数
一个是\(spilt\)保证功能性
另一个是\(assign\)保证其复杂度
\(split(l,r)\)
为了避免像分块那样整块和散块分别遍历 , 珂朵莉树创造了这个分裂函数
可以把一个序列中\([l,r]\)这段区间给独立出来
inline void split(int l,int r)
{
sto it = s.upper_bound({l,l,0}); // 查找左端点所在块
it--;
if((*it).l != l) // 如果l并不是该块的左端点
{
node p = *it;
s.erase(it);
s.insert({p.l , l-1,p.sum});
s.insert({l , p.r , p.sum});//分列
}
it =s.upper_bound({r,r,0});//对r同理
it--;
if((*it).r != r)
{
node p = *it;
s.erase(it);
s.insert({r+1,p.r , p.sum});
s.insert({p.l , r, p.sum});
}
}
\(assigne\)
推平操作,用以保持复杂度
先\(split(l,r)\)然后然后顺序删掉
\(erase\)函数支持区间删除 , 不过是左闭右开的 , 并且你需要传指针
inline void assign(int l,int r,int val)
{
split(l,r);
s.erase(s.lower_bound({l,l,0}) , s.upper_bound({r,r,0}));
s.insert({l,r,val});
}
珂朵莉的灵魂就在于这两个核心函数 , 剩下的则需要考场上随机应变,凭本事骗分了
\(Code\)
#include<bits/stdc++.h>
using namespace std;
#define INF 1ll<<30
#define int long long
#define exp 1e-7
#define db double
#define us unsigned
#define pii pair<int,int>
#define fi first
#define sc second
#define sto set<node>::iterator
const int mod = 1e9+7;
const int p = 3e5+5;
template<typename _T>
inline void read(_T &x)
{
x= 0 ;int f =1;char s=getchar();
while(s<'0' ||s>'9') {f =1;if(s == '-')f =- 1;s=getchar();}
while('0'<=s&&s<='9'){x = (x<<3) + (x<<1) + s - '0';s= getchar();}
x*=f;
}
struct node{
int l,r;
mutable int sum;
friend bool operator <(node A,node B)
{
return A.l < B.l;
}
}a[p],b[p];
set<node> s;
inline void split(int l,int r)
{
sto it = s.upper_bound({l,l,0});
it--;
if((*it).l != l)
{
node p = *it;
s.erase(it);
s.insert({p.l , l-1,p.sum});
s.insert({l , p.r , p.sum});
}
it =s.upper_bound({r,r,0});
it--;
if((*it).r != r)
{
node p = *it;
s.erase(it);
s.insert({r+1,p.r , p.sum});
s.insert({p.l , r, p.sum});
}
}
inline void assign(int l,int r,int val)
{
split(l,r);
s.erase(s.lower_bound({l,l,0}) , s.upper_bound({r,r,0}));
s.insert({l,r,val});
}
inline int query(int l,int r)
{
split(l,r);
sto it = s.lower_bound({l,l,0});
int ans = 0;
for(;it!=s.end() && (*it).r<=r;++it) (ans += (*it).sum * ((*it).r - (*it).l +1))%=mod;
return ans;
}
void add(int l,int r,int val)
{
split(l,r);
sto it = s.lower_bound({l,l,0});
for(;it!=s.end() && (*it).r<=r;++it) ((*it).sum += val)%=mod;
}
inline void copy(int la,int ra,int lb,int rb)
{
split(la,ra);
split(lb,rb);
s.erase(s.lower_bound({lb,lb,0}) , s.upper_bound({rb,rb,0}));
sto it = s.lower_bound({la,la,0});
for( ; it != s.end() && (*it).r <=ra ;++it)
s.insert({lb + (*it).l - la , lb + (*it).r - la , (*it).sum});
}
inline void swap(int la,int ra,int lb,int rb)
{
split(la,ra);
split(lb,rb);
int lena = 0,lenb = 0;
sto ita = s.lower_bound({la,la,0});
sto itb = s.lower_bound({lb,lb,0});
for(;ita!= s.end() && (*ita).r <= ra;ita++) a[++lena] = *ita;
for(;itb!= s.end() && (*itb).r <= rb;itb++) b[++lenb] = *itb;
s.erase(s.lower_bound({la,la,0}) , s.upper_bound({ra,ra,0}));
s.erase(s.lower_bound({lb,lb,0}) , s.upper_bound({rb,rb,0}));
for(int i = 1;i<= lenb;i++)
s.insert({la + b[i].l - lb , la + b[i].r - lb , b[i].sum});
for(int i=1;i<=lena;i++)
s.insert({lb + a[i].l - la , lb + a[i].r - la , a[i].sum});
}
inline void reverse(int l,int r)
{
split(l,r);
sto it = s.lower_bound({l,l,0});
int len = 0;
for(;it != s.end() && (*it).r <=r ;it++) a[++len] = *it;
s.erase(s.lower_bound({l,l,0}) , s.upper_bound({r,r,0}));
for(int i=len;i;i--)
s.insert({l + r - a[i].r , r - a[i].l + l , a[i].sum});
}
inline void write()
{
for(sto i=s.begin();i!=s.end();i++)
{
int l,r,c;
l = (*i).l;
r = (*i).r;
c = (*i).sum;
for(int j=l;j<=r;j++) printf("%lld ",c);
}
printf("\n");
}
signed main()
{
int n,q;
read(n);
read(q);
for(int i=1,x;i<=n;i++)
{
read(x);
s.insert({i,i,x});
}
for(int i=1,opt,l,r,la,lb,ra,rb,c;i<=q;i++)
{
read(opt);
switch(opt)
{
case 1:read(l);read(r);cout<<query(l,r)<<'\n';break;
case 2:read(l);read(r);read(c);assign(l,r,c);break;
case 3:read(l);read(r);read(c);add(l,r,c);break;
case 4:read(la);read(ra);read(lb);read(rb);copy(la,ra,lb,rb);break;
case 5:read(la);read(ra);read(lb);read(rb);swap(la,ra,lb,rb);break;
case 6:read(l);read(r);reverse(l,r);break;
}
}
write();
}
\(End\)
珂朵莉树\(set\)实现的复杂度是\(O(nloglogn)\)
如果您足够闲可以考虑尝试链表实现珂朵莉树——复杂度\(O(nlogn)\)
虽然\(set\)的复杂度更优 , 但考虑到频繁调用\(STL\),所以两者在正常规模下应该是五五开吧
推荐一道珂朵莉树水题