珂朵莉树/颜色段均摊学习笔记
珂朵莉树是基于set的数据结构,与线段树、平衡树等树形结构类似,珂朵莉树是用来解决区间问题的很暴力的树形结构
该数据结构支持区间推平,即将区间\([l,r]\)赋值为同一个值
1. 前置知识 set
set本质是平衡树,所以不会出现重复的元素且会自动排序,部分函数:
set <Node> t; //定义一个名为t,类型为Node(结构体)的set
s.begin(); //返回指向第一个元素的迭代器(地址)
s.end(); //返回指向最后一个元素的迭代器
s.clear(); //清空s
s.insert(a); //将a插入到s
s.erase(l,r); //将迭代器l~r之间的元素全部删除
s.lower_bound(a); //二分查找a在s中的位置,返回一个迭代器。
set<Node>::iterator pos; //定义一个迭代器pos。
2. 实现
2.1. 构造
上文提到珂朵莉树用于解决区间推平操作,那么推平以后一段连续区间的值就是相同的
所以经过若干次推平后,我们可以看成这个序列上的数字是一段一段的,每一小段数字相同
此时,可以使用一个结构体存储数值相同的段
例如序列 1 1 1 1 1 2 2 2 2 3 3 3 3
在珂朵莉树上这个序列被表示为(1, 5, 1) (6, 9, 2) (10, 14, 3)三个节点
struct node{
ll l,r; //区间左端点与右端点
mutable ll v; //mutable为“可变的”,使我们可以直接修改v的值
node(ll l,ll r=0,ll v=0): l(l),r(r),v(v) {}
bool operator < (const node &x) const{
return l<x.l;
}
};//定义一个结构体,重载<为按左端点排序
2.2. 区间分割
进行修改和区间加时,可能就不能在原区间上操作,就需要把它分开
假设要将pos所在的节点以pos为中心分为两个节点,具体步骤如下:
-
二分找到pos所在节点\((l,r,val)\)
-
删除该节点
-
将这个节点分裂成\((l,pos-1,val)\)和\((pos,r,val)\)扔回set
-
返回右区间迭代器
//以pos去做切割,找到一个包含pos的区间,把它分成[l,pos-1],[pos,r]两半
IT split(int pos)
{
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)
{
return it;
}
it--;
if(it->r<pos) return s.end();
ll l=it->l,r=it->r,v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
//insert函数返回pair,其中的first是新插入结点的迭代器
return s.insert(node(pos,r,v)).first;
}
2.3. 区间推平
假设要推平区间\([l,r]\),就要先把它分裂出来,然后将这些区间删掉,然后添加一个新的段即可
void assign(ll l,ll r,ll x)
{
IT itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,x));
}
2.4. 区间加
先将操作区间分裂出来,然后暴力修改即可
void add(ll l,ll r,ll x)
{
IT itr=split(r+1),itl=split(l);
for(IT it=itl;it!=itr;++it)
{
it->v+=x;
}
}
3. 例题
Willem, Chtholly and Seniorious
区间第k小可以将所在区间的段分裂出来,然后按值排序,当size大于等于k时直接输出
求x次方和就可以将所包含的每一段的单个值求出来,在乘个数求和即可
其他按上述求解即可
代码:
#include<bits/stdc++.h>
#define ll long long
#define IT set<node>::iterator
using namespace std;
ll n,m,seed,vmax,a[100005];
struct node{
ll l,r;
mutable ll v;
node(ll l,ll r=0,ll v=0): l(l),r(r),v(v) {}
bool operator < (const node &x) const{
return l<x.l;
}
};
set<node> s;
IT split(int pos)
{
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)
{
return it;
}
it--;
if(it->r<pos) return s.end();
ll l=it->l,r=it->r,v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
void assign(ll l,ll r,ll x)
{
IT itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,x));
}
void add(ll l,ll r,ll x)
{
IT itr=split(r+1),itl=split(l);
for(IT i=itl;i!=itr;++i)
{
i->v+=x;
}
}
struct rnk{
ll num,cnt;
bool operator < (const rnk &a)const{
return num<a.num;
}
rnk(ll num,ll cnt): num(num),cnt(cnt) {}
};
ll getrank(ll l,ll r,ll x)
{
IT itr=split(r+1),itl=split(l);
vector<rnk> v;
v.clear();
for(IT i=itl;i!=itr;++i)
{
v.push_back(rnk(i->v,i->r-i->l+1));
}
sort(v.begin(),v.end());
int i;
for(i=0;i<v.size();i++)
{
if(v[i].cnt<x)
{
x-=v[i].cnt;
}
else break;
}
return v[i].num;
}
ll qpow(ll x,ll y,ll p)
{
x%=p;
ll res=1;
while(y)
{
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
ll getans(ll l,ll r,ll x,ll y)
{
IT itr=split(r+1),itl=split(l);
ll ans=0;
for(IT i=itl;i!=itr;++i)
{
ans=(ans+qpow(i->v,x,y)*(i->r-i->l+1)%y)%y;
}
return ans;
}
ll rnd()
{
ll ret=seed;
seed=(seed*7+13)%1000000007;
return ret;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
for(int i=1;i<=n;i++)
{
a[i]=(rnd()%vmax)+1;
// printf("%d ",a[i]);
s.insert(node(i,i,a[i]));
}
// printf("1");
for(int i=1;i<=m;i++)
{
ll op,l,r,x,y;
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;
else x=(rnd()%vmax)+1;
if (op==4) y=(rnd()%vmax)+1;
// printf("%d %d %d %d\n",op,l,r,x);
if(op==1)
{
add(l,r,x);
}
if(op==2)
{
assign(l,r,x);
}
if(op==3)
{
printf("%lld\n",getrank(l,r,x));
}
if(op==4)
{
printf("%lld\n",getans(l,r,x,y));
}
}
return 0;
}