一天天的为了啥。|

lgh_2009

园龄:1年粉丝:1关注:0

珂朵莉树学习笔记

一类依据区间推平,set保证复杂度的数据结构。

CF896C

思路:

发现这题有两个特点:区间赋值和数据随机。

那么每四个操作就有一个操作 2 ,并且因为 l,r 随机,每次 2 操作都会把一段较长的区间改为相同的数,那么自然可以想到用三个变量 l,r,v 表示这段数值相同的区间的左端点,右端点以及值。

本题中,整个序列可以分为多个这种区间,于是使用结构体存储。

因为有区间赋值,因此每个区间应当是 可修改的 ,于是我们使用C++中的 mutable 来存储值。
为了便于对整个区间进行操作,我们把这些结构体压入一个 set 中(用来保证 O(n loglogn) 的时间复杂度),因此需要定义排序方式。

struct node {
int l,r;
mutable int v;
friend bool operator < (node a, node b) {
return a.l<b.l;
}
};

考虑如何维护题目中的操作。

对于操作1,我们对每个被查询区间中 被完全包围 的区间直接对 v 加上 x

考虑如何处理 不被完全包围 的区间。

我们考虑像线段树一样将区间分为两个区间,但是不同的是,线段树通过将区间分为长度相等的两个区间保证复杂度 ,而珂朵莉树只需要直接把区间 [l,r] 分为 [l,askr][askr,r] 即可。

因此我们可以写出 split 函数的代码:

inline sti split(int pos) {
sti it=s.lower_bound(node{pos,0,0}});//找到左端点
if(it!=s.end() && it->l==pos) return it;//直接包含
//如果不满足1说明当前这个区间比需要的区间大
//如果不满足2说明 pos 超过了最后一个区间的右端点
it--;//往前一个
if(it->r<pos) return s.end();
//如果最后一个的右端点还是比 pos 小就说明 pos 太大,返回 s.end() 即可。
T l=it->l,r=it->r,v=it->v;//进行分割
s.erase(it),s.insert(node(l,pos-1,v));
//删掉原来的,把区间分成[l,pos-1],[pos,r]两段
return s.insert(node(pos,r,v)).first;
//s.insert()返回一个 pair ,第一个就是新插入的结构体的位置
}

对于操作2,考虑将 不被完全包围 的区间分解后直接修改 v 即可。

注意区间赋值函数同样是 保证复杂度的关键。

因为将左右端点之间的 sti 都删除了,所以大大减少了区间数量。

inline void assign(int l, int r, int x) {
sti ir=split(r+1),il=split(l);
s.erase(il,ir);
s.insert(l,r,x);
}

对于操作3,考虑将区间暴力取出丢到一个 vector 里面然后排序。因为区间个数不会太多,所以复杂度大概是 O(log2n) 的。

对于操作4,同样暴力,复杂度也是 O(log2n) 的。

需要注意在修改和查询时,需要先 split 右端点再 split 左端点。因为如果先 split 左端点,存储下来的 sti 可能会在 split 右端点时被删除。

完整代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define sti set<node>::iterator
const ll MOD=1000000007;
const ll MAXN=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&a) const {
return l<a.l;
}
};
ll n,m,seed,vmax,a[MAXN];
set<node>s;
sti split(int pos) {
sti 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;
ll r=it->r;
ll v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
void add(ll l,ll r,ll x) {
sti itr=split(r+1),itl=split(l);
for(sti it=itl;it!=itr;++it)
it->v+=x;
}
void assign(ll l,ll r,ll x) {
sti itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,x));
}
ll rnk(ll l,ll r,ll x) {
sti itr=split(r+1),itl=split(l);
vector<pair<ll,ll> >v;
for(sti i=itl;i!=itr;++i) v.push_back(make_pair(i->v,i->r-i->l+1));
sort(v.begin(),v.end());
int i;
for(i=0;i<v.size();++i)
if(v[i].second<x) x-=v[i].second;
else break;
return v[i].first;
}
ll ksm(ll x,ll y,ll p) {
ll r=1;
ll base=x%p;
while(y) {
if(y&1) r=r*base%p;
base=base*base%p;
y>>=1;
}
return r;
}
ll calP(ll l,ll r,ll x,ll y) {
sti itr=split(r+1),itl=split(l);
ll ans=0;
for(sti i=itl;i!=itr;++i) ans=(ans+ksm(i->v,x,y)*(i->r-i->l+1)%y)%y;
return ans;
}
ll rnd() {
ll ret=seed;
seed=(seed*7+13)%MOD;
return ret;
}
int main() {
cin>>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) {
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;
if(op==1) add(l,r,x);
else if(op==2) assign(l,r,x);
else if(op==3) cout<<rnk(l,r,x)<<endl;
else cout<<calP(l,r,x,y)<<endl;
}
return 0;
}

其实不是很难码,注意 split 就可以了。

练习

1.P3740

使用珂朵莉树每次暴力覆盖即可。

2.CF915E

很套路的珂朵莉树,注意在每次修改的时候记录工作日时间而不是每次扫一遍。

3.P3071

A 操作即为暴力遍历整个 set ,如果有就赋值为1。
L 操作就是区间赋值。

4.P2894

同3

5.CF817F

珂朵莉树维护即可。

6.CF981G

solution

本文作者:lgh_2009

本文链接:https://www.cnblogs.com/lgh-blog/p/18042975

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   lgh_2009  阅读(16)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起