珂朵莉树学习笔记

一个神奇的数据结构 , 当数据随机情况下可以乱杀

又名珂朵莉树

基本概念与条件性

利用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\),所以两者在正常规模下应该是五五开吧

推荐一道珂朵莉树水题

posted @ 2021-09-28 21:57  ·Iris  阅读(88)  评论(0编辑  收藏  举报