珂朵莉树学习笔记

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

CF896C

思路:

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

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

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

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

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({\log}^2 n )\) 的。

对于操作4,同样暴力,复杂度也是 \(O({\log}^2 n )\) 的。

需要注意在修改和查询时,需要先 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

posted @ 2024-02-29 10:59  lgh_2009  阅读(15)  评论(0编辑  收藏  举报