Loading

ODT珂朵莉树

远古文章,懒得修了。

关于ODT,毒瘤lxl发明的,然后毒瘤鱼鱼因为我用ODT误导人D了我一回……upd:这都一年前的事了

这是一种基于 \(set\) 的暴力数据结构。

在使用时请注意,(大部分题目)没看见这2东西千万别用……

  1. 保证数据随机

  2. 有区间推平操作。

主要是均摊复杂度要正确

这里解释一下区间推平:就是把区间 [l,r] 的值都变为val。

然后就是各种暴力:区间翻转、区间求和、区间k次幂和……

板子

先看看它能干什么再往下看qwq(当然ODT的操作远不止这些)。

首先先讲一下基础:set的定义。对于每个节点用 \((l,r,val)\) 描述(所以为什么要区间推平,否则节点数太多)

然后搞个结构体。

struct node {
	int l,r;
	mutable int val;//mutable一定要加,后面会修改set中的值,不加就修改不了。
	node(int L,int R=-1,int V=0):l(L),r(R),val(V){}//看不懂不要紧,后面用上的时候就懂了
	bool operator < (const node &rhs)const{return l<rhs.l;}//按照左端点排序
};
set<node>s;

然后是最重要的函数 \(Split(int pos)\) 。它的作用是找到包含pos的区间 [l,r] ,然后把它分裂成 [l,pos-1], [pos,r] 并且返回pos处的指针。后面操作的时候,往往需要对一段区间进行操作,我们把这段区间Split出来就好了。

#define IT set<node>::iterator
//上面那行最好加上,写起来码量小很多。
IT Split(int pos){
	IT it=s.lower_bound(node(pos));
	if(it!=s.end()&&it->l==pos)return it;
	--it;
	int ll=it->l,rr=it->r,v=it->val;
	s.erase(it);
	s.insert(node(ll,pos-1,v));
	return s.insert(node(pos,rr,v)).first;
}

注意,Split的时候按端点从大到小Split,否则会玄学RE 众所周知,set,尤其是指针,可玄学了

接着是 \(Assign\) ,代码超级短,时间复杂度很低,ODT就是靠这个才能保证时间复杂度的。

void Assign(int l,int r,int val) {
	IT itr=Split(r+1),itl=Split(l);//于是区间 [l,r] 对应的指针就是 [itl,itr)
	s.erase(itl,itr);
	s.insert(node(l,r,val));
}

其余就是各种暴力,还是看上面的题。

1.区间修改。

不要看到这种就想线段树和树状数组,这里在讲ODT呢

ODT设计函数的时候记住一句话:除了Split和Assign,全部暴力

看看代码就懂了qwq

void add(int l,int r,int k)
{
	it itr=split(r+1),itl=split(l);
	for(;itl!=itr;itl++) 
	itl->val+=k;
}

2.区间推平。上面讲过了,略。

3.区间第k小

也不要想着主席树什么的,这里在讲ODT!

int kth(int l,int r,int k)
{
	vector<pair<int,int> >v;
	it itr=split(r+1),itl=split(l);	
	for(;itl!=itr;itl++)v.push_back(pair<int,int>(itl->val,itl->rr-itl->ll+1));
	sort(v.begin(),v.end());//按照权值排序
	for(vector<pair<int,int> >::iterator iter=v.begin();iter!=v.end();iter++)
	{
		k-=iter->second;//不断减,直到达到k。其实可以前缀和+二分做到 O(n),但是不需要
		if(k<=0)return iter->first;
	}
	return -1;
}

相信各位巨佬看完就懂了

4.区间幂次方和。稍微有点复杂。不过这个快速幂的优化一定得加,不然会TLE。

int ksm(int n,int k,int p)
{
	int base=n%p,ans=1;
	while(k)
	{
		if(k&1)ans=ans*base%p;
		k>>=1;
		base=base*base%p;
	}
	return ans%p;
}

int querypow(int l,int r,int k,int mod)
{
	it itr=split(r+1),itl=split(l);
	int res=0;
	for(;itl!=itr;itl++)
		res=(res+ksm(itl->val,k,mod)*((itl->rr-itl->ll+1)%mod)%mod)%mod;
	return res;
}

不要忘记乘长度就好了。

完整代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=100005;
int n,m,seed,vmax,a[MAXN];
int rnd()
{
	int ret=seed;
	seed=(seed*7+13)%1000000007;
	return ret;
}
int ksm(int n,int k,int p)
{
	int base=n%p,ans=1;
	while(k)
	{
		if(k&1)ans=ans*base%p;
		k>>=1;
		base=base*base%p;
	}
	return ans%p;
}
struct node{
	int ll,rr;
	mutable int val;
	node(int L,int R=-1,int V=0):ll(L),rr(R),val(V){}
	bool operator < (const node & rhs)const{return ll<rhs.ll;}
};
set<node>s;
#define it set<node>::iterator
it split(int pos)
{
	it iter=s.lower_bound(node(pos));
	if(iter!=s.end()&&iter->ll==pos)return iter;
	--iter;
	int l=iter->ll,r=iter->rr,v=iter->val;
	s.erase(iter);
	s.insert(node(l,pos-1,v));
	return s.insert(node(pos,r,v)).first;
}
void assign(int l,int r,int v)
{
	it itr=split(r+1),itl=split(l);
	s.erase(itl,itr);
	s.insert(node(l,r,v));
}
int kth(int l,int r,int k)
{
	vector<pair<int,int> >v;
	it itr=split(r+1),itl=split(l);	
	for(;itl!=itr;itl++)v.push_back(pair<int,int>(itl->val,itl->rr-itl->ll+1));
	sort(v.begin(),v.end());
	for(vector<pair<int,int> >::iterator iter=v.begin();iter!=v.end();iter++)
	{
		k-=iter->second;
		if(k<=0)return iter->first;
	}
	return -1;
}
void add(int l,int r,int k)
{
	it itr=split(r+1),itl=split(l);
	for(;itl!=itr;itl++) 
	itl->val+=k;
}
int querypow(int l,int r,int k,int mod)
{
	it itr=split(r+1),itl=split(l);
	int res=0;
	for(;itl!=itr;itl++)
		res=(res+ksm(itl->val,k,mod)*((itl->rr-itl->ll+1)%mod)%mod)%mod;
	return res;
}

signed main() {
	scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
	for(int i=1;i<=n;i++)
	{
		a[i]=(rnd()%vmax)+1;
		s.insert(node(i,i,a[i]));
	}
	for(int i=1;i<=m;i++)
	{
		int opt=(rnd()%4)+1;
		int l=(rnd()%n)+1;
		int r=(rnd()%n)+1;
		if(l>r)swap(l,r);
		int x,y;
		if(opt==3)x=(rnd()%(r-l+1))+1;
		else x=(rnd()%vmax)+1;
		if(opt==4)y=(rnd()%vmax)+1;
		if(opt==1)add(l,r,x);
		if(opt==2)assign(l,r,x);
		if(opt==3)printf("%lld\n",kth(l,r,x));
		if(opt==4)printf("%lld\n",querypow(l,r,x,y));
	}
	return 0;
}

然后是第2道例题:P5350 序列

不保证数据不会加强,所以很可能已经过不去了,就自己看看吧

看看一堆操作,使人一下想到了ODT(发现毒瘤鱼鱼的平衡树是第一篇题解……)

1.区间求和,上面都有,略。

2.区间推平,上面都有,略。

3.区间加法,上面都有,略。

4.区间复制。上面没有,qwq

int a[N],b[N],c[N],tot;
void Copy(int l1,int r1,int l2,int r2) {
	IT itr=Split(r1+1),itl=Split(l1);
	for(tot=0;itl!=itr;++itl)
		a[++tot]=l2+itl->l-l1,b[tot]=l2+itl->r-l1,c[tot]=itl->val;
	for(int i=1;i<=tot;++i)
		Assign(a[i],b[i],c[i]);
}

关于我为什么拿了3个数组存:set的指针尤其玄学,还是不要对指针进行操作了,而且代码也很短。

5.区间交换。

void Swap(int l1,int r1,int l2,int r2) {
	Copy(l1,r1,n+1,n+r1-l1+1);
	Copy(l2,r2,l1,r1);
	Copy(n+1,n+r1-l1+1,l2,r2);
}

在这个序列后加一个长为n的空序列,充分发挥已经写过的函数的作用,很方便。

6.区间翻转。

void Reverse(int l,int r) {
	IT itr=Split(r+1),itl=Split(l);
	vector<node>vec;
	for(IT it=itl;it!=itr;++it)
		vec.push_back(node(it->l,it->r,it->val));
	s.erase(itl,itr);
	int t=l;
	for(int i=vec.size()-1;i>=0;--i)
		s.insert(node(t,t+vec[i].r-vec[i].l,vec[i].val)),t+=vec[i].r-vec[i].l+1;
}

看看代码应该挺好懂……

7.关于输出(Print)。由于节点代表的是区间,所以可以这么写。

void Print(){
	IT it=s.begin();
	int print=0;
	while(print<n) {
		for(int i=it->l;i<=it->r;++i)
		{
			printf("%lld ",it->val);
			++print;
			if(print==n)return (void)puts("");
		}
		++it;
	}
}

然后整合起来:千万记得开O2! 调了1h结果发现是O2的问题,气死我了

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1000000007;
const int N=300005;
int n,m;
struct node {
	int l,r;
	mutable int val;
	node(int L,int R=-1,int V=0):l(L),r(R),val(V){}
	bool operator < (const node &rhs)const{return l<rhs.l;}
};
set<node>s;
#define IT set<node>::iterator
void Print(){
	IT it=s.begin();
	int print=0;
	while(print<n) {
		for(int i=it->l;i<=it->r;++i)
		{
			printf("%lld ",it->val);
			++print;
			if(print==n)return (void)puts("");
		}
		++it;
	}
}
IT Split(int pos){
	IT it=s.lower_bound(node(pos));
	if(it!=s.end()&&it->l==pos)return it;
	--it;
	int ll=it->l,rr=it->r,v=it->val;
	s.erase(it);
	s.insert(node(ll,pos-1,v));
	return s.insert(node(pos,rr,v)).first;
}
int Sum(int l,int r){
	int res=0;
	IT itr=Split(r+1),itl=Split(l);
	for(;itl!=itr;++itl)
		res=(res+(itl->r-itl->l+1)*itl->val)%mod;
	return (int)res;
}
void Assign(int l,int r,int val) {
	IT itr=Split(r+1),itl=Split(l);
	s.erase(itl,itr);
	s.insert(node(l,r,val));
}
void Add(int l,int r,int val) {
	IT itr=Split(r+1),itl=Split(l);
	for(;itl!=itr;++itl)
		itl->val=(itl->val+val)%mod;
}
int a[N],b[N],c[N],tot;
void Copy(int l1,int r1,int l2,int r2) {
	IT itr=Split(r1+1),itl=Split(l1);
	for(tot=0;itl!=itr;++itl)
		a[++tot]=l2+itl->l-l1,b[tot]=l2+itl->r-l1,c[tot]=itl->val;
	for(int i=1;i<=tot;++i)
		Assign(a[i],b[i],c[i]);
}
void Swap(int l1,int r1,int l2,int r2) {
	Copy(l1,r1,n+1,n+r1-l1+1);
	Copy(l2,r2,l1,r1);
	Copy(n+1,n+r1-l1+1,l2,r2);
}
void Reverse(int l,int r) {
	IT itr=Split(r+1),itl=Split(l);
	vector<node>vec;
	for(IT it=itl;it!=itr;++it)
		vec.push_back(node(it->l,it->r,it->val));
	s.erase(itl,itr);
	int t=l;
	for(int i=vec.size()-1;i>=0;--i)
		s.insert(node(t,t+vec[i].r-vec[i].l,vec[i].val)),t+=vec[i].r-vec[i].l+1;
}
signed main() {
	scanf("%lld%lld",&n,&m);
	for(int i=1,x;i<=n;++i)
		scanf("%d",&x),s.insert(node(i,i,x));
	s.insert(node(n+1,n+n+5,0));
	while(m--) {
		int opt;
		scanf("%lld",&opt);
		if(opt==1) {
			int l,r;
			scanf("%lld%lld",&l,&r);
			printf("%d\n",Sum(l,r));
		}
		if(opt==2) {
			int l,r,val;
			scanf("%lld%lld%lld",&l,&r,&val);
			Assign(l,r,val);
		}
		if(opt==3) {
			int l,r,val;
			scanf("%lld%lld%lld",&l,&r,&val);
			Add(l,r,val);
		}
		if(opt==4) {
			int l1,r1,l2,r2;
			scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
			Copy(l1,r1,l2,r2);
			}
		if(opt==5) {
			int l1,r1,l2,r2;
			scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
			Swap(l1,r1,l2,r2);
		}
		if(opt==6) {
			int l,r;
			scanf("%lld%lld",&l,&r);
			Reverse(l,r);
		}
	}
	Print();
	return 0;
}

反正ODT是个玄学的东西,set的指针要尤其小心。但是思路简单,时间复杂度在数据随机且有区间推平的时候很靠谱。

nmd一年之后重看文章发现写一个数据结构连时间复杂度都不证,我tm真是傻逼

nmd一年之后重看文章发现我还是不会证复杂度,我tm真是傻逼

posted @ 2020-02-27 21:59  zzctommy  阅读(224)  评论(0编辑  收藏  举报