神奇的珂朵莉树

珂朵莉镇楼qwq
在这里插入图片描述

算法基础

这个算法的基础很神奇,大家想一下这样一个问题:

有一个长度为 n n 的序列,随机选出一个区间,这个区间的期望长度是?

(要是会的话就跳过做法吧)

做法

考虑左端点在 1 1 位置的情况,那么区间期望长度为: 1 + n 2 \frac {1+n} 2 ,这种情况的出现频率为 n n

考虑左端点在 2 2 位置的情况,那么区间期望长度为: n 2 \frac n 2 ,这种情况的出现概率为 n 1 n-1

……

那么最终的期望就是
( 1 + n ) × n 2 + n × ( n 1 ) 2 + ( n 1 ) × ( n 2 ) 2 . . . + ( 1 + 1 ) × 1 2 n ( n + 1 ) 2 = ( n + 1 ) n + n ( n 1 ) + ( n 1 ) ( n 2 ) + . . . + 2 × 1 n ( n + 1 ) = 1 × 2 + 2 × 3 + 3 × 4 + . . . n ( n + 1 ) = ( 1 2 + 2 2 + 3 2 + . . . + n 2 ) + ( 1 + 2 + 3 + . . . + n ) n ( n + 1 ) = n ( n + 1 ) ( 2 n + 1 ) / 6 + n ( n + 1 ) / 2 n ( n + 1 ) = ( 2 n + 1 ) / 6 + 1 / 2 = 1 3 n + 1 6 + 1 2 \begin{aligned} &\frac {\frac {(1+n) \times n} 2 + \frac {n \times (n-1)} 2 + \frac {(n-1) \times (n-2)} 2...+\frac {(1+1)\times 1} 2} {\frac {n(n+1)} 2}\\ &=\frac {(n+1)n+n(n-1)+(n-1)(n-2)+...+2\times 1} {n(n+1)}\\ &=\frac {1\times 2 +2\times 3 + 3\times 4 +...} {n(n+1)}\\ &=\frac {(1^2+2^2+3^2+...+n^2)+(1+2+3+...+n)} {n(n+1)}\\ &=\frac {n(n+1)(2n+1)/6+n(n+1)/2} {n(n+1)}\\ &=(2n+1)/6+1/2\\ &=\frac 1 3 n +\frac 1 6+\frac 1 2 \end{aligned}

舍去后面两个跟没有一样的分数,可以得出,随机选出一个区间,这个区间的期望长度为 1 3 n \frac 1 3 n

这便是珂朵莉树的基础。

正题

使用前提

使用珂朵莉树的前提有两个:

  1. 数据随机(这样上面那个性质就可以用了!)
  2. 将某个区间都修改为某个值这样的操作

核心思想

将序列中值相同的一段区间压成一个点。

比如说,有一个序列 1   1   1   1   1   1   2   2   3   3   3   3 1~1~1~1~1~1~2~2~3~3~3~3

那么会压成三个点,这三个点是: ( 1 , 6 , 1 )   ,   ( 7 , 8 , 2 )   ,   ( 9 , 12 , 3 ) (1,6,1)~,~(7,8,2)~,~(9,12,3)

其中 ( a , b , c ) (a,b,c) 表示序列中区间 [ a , b ] [a,b] 的值都是 c c

然后这些点我们用 s e t set 维护一下就好了。

因为有将某个区间都修改为某个值这样的操作,并且数据随机,那么我们每一次就可以将整个序列中长度为 1 3 n \frac 1 3 n 的区间改成一个相同的值,然后我们可以将这个区间压成一个点。

然后因为操作种数不是很多,以模板题为例,只有 4 4 种操作,那么在随机情况下,每 4 4 个操作里面就有一个区间修改操作,那么这就注定了这个序列不会被压成太多点,点数大约是 l o g ( n ) log(n) 个的,然后算上 s e t set 的时间复杂度,那么总的时间复杂度就是 O ( n l o g ( n ) l o g ( l o g ( n ) ) ) O(nlog(n)log(log(n)))

先贴出的结构体代码:

struct node{
	int l,r;
	mutable ll val;
	//这个mutable是为了保证我们可以在set里面修改它
	node(int L,int R=0,ll v=0):l(L),r(R),val(v){};
	bool operator <(const node b)const{return l<b.l;}//用来给set排序
};

核心操作

split

s p l i t ( p o s ) split(pos) 这样的操作是将包含 p o s pos 这个位置的点拆成分别管理 [ l , p o s 1 ] [l,pos-1] [ p o s , r ] [pos,r] 的两个点,并且返回管理 [ p o s , r ] [pos,r] 的这个点。这个返回值后面有大用处。

代码如下:

//完整代码中已经将it这个东西define成set<node>::iterator了
it split(int pos)
{
	it p=f.lower_bound(node(pos));//找到第一个左端点大于等于pos的点
	if(p!=f.end()&&p->l==pos)return p;//假如找到的这个点刚好以pos作为左端点,那么就不用拆了
	p--;//否则将p--,找到上一块,这一块一定包含pos
	int l=p->l,r=p->r;ll val=p->val;//记录下这一块的信息
	f.erase(p);//删掉这一块
	f.insert(node(l,pos-1,val));//分成两块加回去
	return f.insert(node(pos,r,val)).first;
	//set的insert函数是有返回值的,会返回一个pair,其中first是插入的值在set中的指针,second是 是否插入成功
}

assign

这是区间推平操作,也就是上面的将某个区间都修改为某个值

代码如下:

void assign(int l,int r,int val)
{
	it x=split(l),y=split(r+1);
	//分裂出l,r位置,注意这里的y是指向包含r+1这个位置的,而不是包含r这个位置的
	f.erase(x,y);//将管理[l,r]这一个区间的所有点删除,下面会讲这个函数
	f.insert(node(l,r,val));//压成一个点加回进去
}

s e t set 是一个相当方便的东西,里面这个 e r a s e erase 函数就有不少用法,上面用到的是 e r a s e ( f i r s t , l a s t ) erase(first,last) ,他会帮你删除 s e t set 中的区间 [ f i r s t , l a s t ) [first,last)


核心操作讲完了,接下来顺便讲讲模板题的剩下两个操作。

求区间第k小

由于点数大约是 l o g ( n ) log(n) 级别的,所以我们直接把这个区间掏出来暴力排序找就是了。

代码如下:

struct point{
	ll x;int y;//表示x这个值出现过y次
	point(ll xx,int yy):x(xx),y(yy){}
	bool operator <(const point b)const{return x<b.x;}
};
ll findkth(int x,int y,int k)
{
	it l=split(x),r=split(y+1);//依然要先分离出这个区间
	vector<point>vec;
	for(;l!=r;l++)//遍历这个区间,用vector存起来
	vec.push_back(point(l->val,l->r-l->l+1));
	sort(vec.begin(),vec.end());//排序
	for(int i=0;i<vec.size();i++)
	{
		k-=vec[i].y;//减去出现次数
		if(k<=0)return vec[i].x;//减没了说明就是这个数
	}
}

求区间 x x 次方对 y y 取模的值

依然是暴力乱做:

ll ksm(ll x,int y,int mod)
{
	ll re=1,tot=x%mod;
	while(y)
	{
		if(y&1)re=re*tot%mod;
		tot=tot*tot%mod;
		y>>=1;
	}
	return re;
}
ll ask(int x,int y,int p,int mod)
{
	ll ans=0;
	it l=split(x),r=split(y+1);//分离
	for(;l!=r;l++)//遍历
	ans=(ans+(ll)(l->r-l->l+1)%mod*ksm(l->val,p,mod))%mod;//求解
	return ans;
}

最后是模板题的 A C AC 代码:

#include <cstdio>
#include <cstring>
#include <set>
#include <vector>
#include <algorithm>
using namespace std;
#define ll long long
#define it set<node>::iterator

int n,m,vmax;
ll seed;
int rnd()
{
	int re=seed;
	seed=(seed*7ll+13ll)%1000000007;
	return re;
}
struct node{
	int l,r;
	mutable ll val;
	node(int L,int R=0,ll v=0):l(L),r(R),val(v){};
	bool operator <(const node b)const{return l<b.l;}
};
set <node>f;
it split(int pos)
{
	it p=f.lower_bound(node(pos));
	if(p!=f.end()&&p->l==pos)return p;
	p--;
	int l=p->l,r=p->r;ll val=p->val;
	f.erase(p);
	f.insert(node(l,pos-1,val));
	return f.insert(node(pos,r,val)).first;
}
void assign(int l,int r,int val)
{
	it x=split(l),y=split(r+1);
	f.erase(x,y);
	f.insert(node(l,r,val));
}
void add(int l,int r,int val)
{
	it x=split(l),y=split(r+1);
	for(;x!=y;x++)x->val+=val;
}
struct point{
	ll x;int y;
	point(ll xx,int yy):x(xx),y(yy){}
	bool operator <(const point b)const{return x<b.x;}
};
ll findkth(int x,int y,int k)
{
	it l=split(x),r=split(y+1);
	vector<point>vec;
	for(;l!=r;l++)
	vec.push_back(point(l->val,l->r-l->l+1));
	sort(vec.begin(),vec.end());
	for(int i=0;i<vec.size();i++)
	{
		k-=vec[i].y;
		if(k<=0)return vec[i].x;
	}
}
ll ksm(ll x,int y,int mod)
{
	ll re=1,tot=x%mod;
	while(y)
	{
		if(y&1)re=re*tot%mod;
		tot=tot*tot%mod;
		y>>=1;
	}
	return re;
}
ll ask(int x,int y,int p,int mod)
{
	ll ans=0;
	it l=split(x),r=split(y+1);
	for(;l!=r;l++)
	ans=(ans+(ll)(l->r-l->l+1)%mod*ksm(l->val,p,mod))%mod;
	return ans;
}

int main()
{
	scanf("%d %d %lld %d",&n,&m,&seed,&vmax);
	for(int i=1,x;i<=n;i++)
	f.insert(node(i,i,rnd()%vmax+1));
	f.insert(node(n+1,n+1,0));//处理边界
	for(int i=1,op,l,r,x,y;i<=m;i++)
	{
		op=rnd()%4+1;l=rnd()%n+1;r=rnd()%n+1;
		if(l>r)swap(l,r);
		if(op==3)
		{
			x=rnd()%(r-l+1)+1;
			printf("%lld\n",findkth(l,r,x));
		}
		else
		{
			x=rnd()%vmax+1;
			if(op==1)add(l,r,x);
			if(op==2)assign(l,r,x);
		}
		if(op==4)
		{
			y=rnd()%vmax+1;
			printf("%lld\n",ask(l,r,x,y));
		}
	}
}
发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/102771654