珂朵莉树学习笔记
一类依据区间推平,set保证复杂度的数据结构。
CF896C
思路:
发现这题有两个特点:区间赋值和数据随机。
那么每四个操作就有一个操作 2
,并且因为 2
操作都会把一段较长的区间改为相同的数,那么自然可以想到用三个变量
本题中,整个序列可以分为多个这种区间,于是使用结构体存储。
因为有区间赋值,因此每个区间应当是 可修改的 ,于是我们使用C++中的 mutable
来存储值。
为了便于对整个区间进行操作,我们把这些结构体压入一个 set
中(用来保证
struct node { int l,r; mutable int v; friend bool operator < (node a, node b) { return a.l<b.l; } };
考虑如何维护题目中的操作。
对于操作1,我们对每个被查询区间中 被完全包围 的区间直接对 v
加上 x
。
考虑如何处理 不被完全包围 的区间。
我们考虑像线段树一样将区间分为两个区间,但是不同的是,线段树通过将区间分为长度相等的两个区间保证复杂度 ,而珂朵莉树只需要直接把区间
因此我们可以写出 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
里面然后排序。因为区间个数不会太多,所以复杂度大概是
对于操作4,同样暴力,复杂度也是
需要注意在修改和查询时,需要先 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
本文作者:lgh_2009
本文链接:https://www.cnblogs.com/lgh-blog/p/18042975
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步