【模板】区间推平(珂朵莉树)

一、弱化版推平——转化为乘0操作,普通线段树即可解决

P1840 Color the Axis

题目描述

在一条数轴上有 n n n 个点,分别是 1 , 2 , … , n 1,2,\ldots,n 1,2,,n。一开始所有的点都被染成黑色。接着我们进行 m m m 次操作,第 i i i 次操作将 [ l i , r i ] [l_i,r_i] [li,ri] 这些点染成白色。请输出每个操作执行后剩余黑色点的个数。

  • 对于 100 % 100\% 100% 的数据,有 1 ≤ l i ≤ r i ≤ n ≤ 2 × 1 0 5 1\le l_i\le r_i\le n\le 2\times 10^5 1lirin2×105 1 ≤ m ≤ 2 × 1 0 5 1\le m\le 2\times10^5 1m2×105

解题思路

s t e p 1 step1 step1 :首先,我们知道,任何数乘0都等于0,乘多少遍都等于0。而这一性质,可以解决染色区间可重复的问题。
s t e p 2 step2 step2:设黑色为1,白色为0,区间染色操作转化为区间乘0操作。这个可以用最普通的线段树实现。

代码

#include<bits/stdc++.h>
using namespace std;
int a[200005],sz[1000005],mul[1000005],k;
int n,m,x,y;
void build(int rt,int l,int r){
	mul[rt]=1;
	if(l==r){sz[rt]=a[l];return;}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	sz[rt]=sz[rt<<1]+sz[rt<<1|1];
}
void pushdown(int rt,int l,int r){
	int mid=(l+r)>>1;
	sz[rt<<1]=sz[rt<<1]*mul[rt];
	sz[rt<<1|1]=sz[rt<<1|1]*mul[rt];
	mul[rt<<1]=mul[rt<<1]*mul[rt];
	mul[rt<<1|1]=mul[rt<<1|1]*mul[rt];
	mul[rt]=1;
	return;
}
void update(int rt,int l,int r,int lpos,int rpos,int k){
	if(rpos<l||r<lpos)return;
	if(lpos<=l&&r<=rpos){
		sz[rt]=sz[rt]*k;mul[rt]=mul[rt]*k;
		return;
	}
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	update(rt<<1,l,mid,lpos,rpos,k);
	update(rt<<1|1,mid+1,r,lpos,rpos,k);
	sz[rt]=sz[rt<<1]+sz[rt<<1|1];
	return;
}
int query(int rt,int l,int r,int lpos,int rpos){
	if(rpos<l||r<lpos)return 0;
	if(lpos<=l&&r<=rpos)return sz[rt];
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	return query(rt<<1,l,mid,lpos,rpos)+query(rt<<1|1,mid+1,r,lpos,rpos);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)a[i]=1;
	build(1,1,n);
	while(m--){
		scanf("%d%d",&x,&y);
		update(1,1,n,x,y,0);
	    printf("%d\n",sz[1]);
	}
	return 0;
}

对了,如果不会乘法线段树,可以先去做这个模板

二、当然了,上道题只是热身,不是所有题都有这种特殊的性质可以巧妙解决。所以,下面我们隆重推出本场主角:珂朵莉树

讲解

在进行完区间推平操作后,数列将被分为几段,每段数字相同,如:222443666。我们针对这一特点,引入珂朵莉树。

  • 珂朵莉树,是一种基于std::set的暴力数据结构,适用于区间推平、数据随机的题目

我们对于上述的,数字相同的每段,用一个结构体维护, l , r l,r l,r,表示这一段的起点和终点,v表示这一段上所有元素相同的值是多少,然后把这些段放到set里,组成这个序列

拆开:split操作,可将某个node维护的段拆开,改变一部分的值
合并:assign操作,可将set中需要被合并的node删除,然后插入一个新的node表示推平

如果有不会用set的同学,可以 点击这里学习

例题

CF896C Willem, Chtholly and Seniorious(珂朵莉树模板)

请你写一种数据结构,支持:

  • 1 1 1 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数加上 x x x
  • 2 2 2 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数改成 x x x
  • 3 3 3 l l l r r r x x x :输出将 [ l , r ] [l,r] [l,r] 区间从小到大排序后的第 x x x 个数是的多少(即区间第 x x x 小,数字大小相同算多次,保证 1 ≤ 1\leq 1 x x x ≤ \leq r − l + 1 r-l+1 rl+1 )
  • 4 4 4 l l l r r r x x x y y y :输出 [ l , r ] [l,r] [l,r] 区间每个数字的 x x x 次方的和模 y y y 的值(即( ∑ i = l r a i x \sum^r_{i=l}a_i^x i=lraix ) m o d    y \mod y mody )

代码

#include <bits/stdc++.h>
#define ll long long
#define  It set<node>::iterator
using namespace std;
const ll mod=1e9+7;
ll seed,vmax,n,m;
int rnd(){
	ll ret=seed;
	seed=(seed*7+13)%mod;
	return ret;
}
ll ksm(ll a,ll b,ll p){
	ll res=1,tmp=a%p;
	while(b){
		if(b&1)res=(res*tmp)%p;
		tmp=(tmp*tmp)%p;
		b>>=1;
	}
	return res;
}
struct node{
	int L,R;
	mutable ll val;
	node(int l,int r=-1,ll v=0):L(l),R(r),val(v){}
	bool operator < (node a) const{
		return L<a.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;
	int l=it->L,r=it->R;
	ll v=it->val;
	s.erase(it);
	s.insert(node(l,pos-1,v));
	return s.insert(node(pos,r,v)).first;
}
void assign(int l,int r,ll v){
	It itr=split(r+1),itl=split(l);
	s.erase(itl,itr);
	s.insert(node(l,r,v));
}
void add(int l,int r,ll v){
	It itr=split(r+1),itl=split(l);
	while(itl!=itr)itl->val+=v,++itl;
}
ll kth(int l,int r,int k){
	vector< pair<ll,int> > v;
	It itr=split(r+1),itl=split(l);
	v.clear();
	while(itl!=itr)v.push_back(make_pair(itl->val,(itl->R)-(itl->L)+1)),++itl;
	sort(v.begin(),v.end());
	for(vector< pair<ll,int> >::iterator it=v.begin();it!=v.end();++it){
		k-=it->second;
		if(k<=0)return it->first;
	}
	return -1;
}
ll sum(int l,int r,ll x,ll y){
	It itr=split(r+1),itl=split(l);
	ll ans=0;
	while(itl!=itr){
		ans+=((itl->R)-(itl->L)+1)*ksm(itl->val,x,y)%y;
		ans%=y;++itl;
	}
	return ans;
}
int main(){
	scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
	for(int i=1;i<=n;i++)s.insert(node(i,i,rnd()%vmax+1));
	while(m--){
		ll opt=(rnd()%4)+1,l=(rnd()%n)+1,r=(rnd()%n)+1,x,y;
		if(l>r) swap(l,r);
		if(opt==1) {
			x=(rnd()%vmax)+1;
			add(l,r,1ll*x);
		}
		else if(opt==2) {
			x=(rnd()%vmax)+1;
			assign(l,r,1ll*x);
		}
		else if(opt==3) {
			x=(rnd()%(r-l+1))+1;
			printf("%lld\n",kth(l,r,x));
		}
		else{
			x=(rnd()%vmax)+1,y=(rnd()%vmax)+1;
			printf("%lld\n",sum(l,r,x,y));
		}
	}
	return 0;
}
``
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个朵莉树模板代码,供参考: ```c++ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 1e5 + 5; struct KDT { int l, r; int lazy; int minv, maxv; } kdt[MAXN * 4]; // 线段树节点 int n, m; int a[MAXN]; void pushup(int rt) { kdt[rt].minv = min(kdt[rt << 1].minv, kdt[rt << 1 | 1].minv); kdt[rt].maxv = max(kdt[rt << 1].maxv, kdt[rt << 1 | 1].maxv); } void pushdown(int rt) { if (kdt[rt].lazy != -1) { kdt[rt << 1].lazy = kdt[rt].lazy; kdt[rt << 1 | 1].lazy = kdt[rt].lazy; kdt[rt << 1].minv = kdt[rt].lazy; kdt[rt << 1].maxv = kdt[rt].lazy; kdt[rt << 1 | 1].minv = kdt[rt].lazy; kdt[rt << 1 | 1].maxv = kdt[rt].lazy; kdt[rt].lazy = -1; } } void build(int l, int r, int rt) { kdt[rt].l = l; kdt[rt].r = r; kdt[rt].lazy = -1; if (l == r) { kdt[rt].minv = kdt[rt].maxv = a[l]; return; } int mid = (l + r) >> 1; build(l, mid, rt << 1); build(mid + 1, r, rt << 1 | 1); pushup(rt); } void update(int L, int R, int c, int rt) { if (L <= kdt[rt].l && kdt[rt].r <= R) { kdt[rt].lazy = c; kdt[rt].minv = c; kdt[rt].maxv = c; return; } pushdown(rt); int mid = (kdt[rt].l + kdt[rt].r) >> 1; if (L <= mid) update(L, R, c, rt << 1); if (R > mid) update(L, R, c, rt << 1 | 1); pushup(rt); } int query_min(int L, int R, int rt) { if (L <= kdt[rt].l && kdt[rt].r <= R) { return kdt[rt].minv; } pushdown(rt); int mid = (kdt[rt].l + kdt[rt].r) >> 1; int res = 2147483647; if (L <= mid) res = min(res, query_min(L, R, rt << 1)); if (R > mid) res = min(res, query_min(L, R, rt << 1 | 1)); return res; } int query_max(int L, int R, int rt) { if (L <= kdt[rt].l && kdt[rt].r <= R) { return kdt[rt].maxv; } pushdown(rt); int mid = (kdt[rt].l + kdt[rt].r) >> 1; int res = -2147483647; if (L <= mid) res = max(res, query_max(L, R, rt << 1)); if (R > mid) res = max(res, query_max(L, R, rt << 1 | 1)); return res; } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> a[i]; } build(1, n, 1); for (int i = 1; i <= m; i++) { int opt, l, r, c; cin >> opt >> l >> r; if (opt == 1) { cin >> c; update(l, r, c, 1); } else if (opt == 2) { cout << query_min(l, r, 1) << endl; } else { cout << query_max(l, r, 1) << endl; } } return 0; } ``` 这个模板代码实现了朵莉树的基本操作,包括建树、更新、查询最小值和查询最大值。你可以根据实际需要进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值