带修莫队

顾名思义,带有修改的莫队。
莫队是一个离线算法,如果用强制在线的问题就不用考虑莫队了(可以树树树?)。
如果用莫队算法求解,必须离线,先把查询操作和修改操作分别记录下来。记录查询操作的时候,增加一个变量,记录本次查询前做了多少次修改。
加上时间轴的带修改莫队
如果没有修改,就是基础莫队,一个查询的左右端点是 [ L , R ] [L, R] [L,R]。加上修改之后,一个查询表示为 ( L , R , t ) (L, R, t) (L,R,t) t t t表示在查询 [ L , R ] [L, R] [L,R]前进行了 t t t次修改操作。可以把t理解为“时间”, t t t的范围是 1 ≤ t ≤ m 1 ≤ t ≤ m 1tm m m m是操作次数。
从一个查询移动到另一个查询,除了 L 、 R L、R LR发生变化外,还要考虑 t t t的变化。如果两个查询的 t t t相同,说明它们是基于同样的数列;如果 t t t不同,两个查询所对应的数列是不同的,那么就需要补上这变化(类似于莫队 L , R L,R L,R修改,只是根据操作次数考虑怎么修改操作的影响,可以想象成离线下来后用时间轴前推后推)。两个查询的 t t t相差越小,它们对应的数列差别越小,计算量也越小,所以对 t t t排序能减少计算量。
算法复杂度几何解释(类似普通莫队)
与基础莫队一样,也可以给出带修改莫队的几何解释。基础莫队的左右端点 [ L , R ] [L, R] [L,R],对应平面上的点 ( L , R ) ( L,R) (L,R) ,带修改的莫队 ( L , R , t ) (L, R, t) (L,R,t)对应立体空间的 ( L , R , t ) (L,R,t ) (L,R,t)。每个查询对应立体空间的一个点,那么从一个查询到另一个查询,就是从一个点到另一个点。计算复杂度仍然是两点之间的曼哈顿距离。
带修莫队排序步骤
模仿基础莫队的分块思路。定义带修改莫队的排序,按以下步骤执行:
( 1 ) (1) 1按左端点 L L L排序。若左端点 L L L在同一个块,执行 ( 2 ) (2) 2 L L L对应 x x x轴。
( 2 ) (2) 2按右端点 R R R排序。若右端点 R R R在同一个块,执行 ( 3 ) (3) 3 R R R对应 y y y轴。
( 3 ) (3) 3按时间 t t t排序。 t t t对应 z z z轴。
左端点 L L L所在的块是第 1 1 1查询关键字,右端点 R R R所在的块是第 2 2 2关键字,时间 t t t是第 3 3 3关键字。
块的大小
具体请看这篇
取block= n 2 3 n^{\frac {2}{3}} n32

注意:针对带修改莫队的排序步骤,因为 t t t是第三关键字,所以奇偶性排序都是针对 t t t操作的。修改分为两部分:1.若修改的位置在当前区间内,需要更新答案(del原颜色,add修改后的颜色)。2.无论修改的位置是否在当前区间内,都要进行修改(以供add和del函数在以后更新答案)。

一道例题:数颜色
不卡常了,算了

#include <bits/stdc++.h>
using namespace std;
const int N=(int)1e6+50;
int n,m,a[N],block,ans[N],pos[N];
int cnt1,cnt2,L=1,R=0,now=0,Ans,cnt[N];
char c[10];
struct change{int P,col;}p[N];
struct node{
	int L,R,t,id;
}q[N];
inline void del(int x){
	--cnt[x];
	if(cnt[x]==0) --Ans; 
}
inline void add(int x){
	++cnt[x];
	if(cnt[x]==1) ++Ans;
}
inline void modify(int x,int ti){
	if(p[ti].P>=q[x].L&&p[ti].P<=q[x].R){
		del(a[p[ti].P]);
		add(p[ti].col);
	}
	swap(a[p[ti].P],p[ti].col);
}
inline bool cmp(node a,node b){
	if(pos[a.L]!=pos[b.L]) return a.L<b.L;
	else if(a.R!=b.R) return a.R<b.R;
	else if(a.R==b.R) return a.t>b.t;
	else return a.t<b.t;
}
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
	return cnt*f;
}
signed main(){
	ios::sync_with_stdio(false);
	n=read(),m=read();block=pow(n,0.666666);
	for(int i=1;i<=n;++i){a[i]=read();pos[i]=(i-1)/block+1;}
	for(int i=1;i<=m;++i){
		scanf("%s",c+1);//cnt2代表当前修改数  t代表在这次询问之前修改了多少次 
		if(c[1]=='Q'){q[++cnt1].L=read(),q[cnt1].R=read();q[cnt1].t=cnt2;q[cnt1].id=cnt1;}
		if(c[1]=='R'){p[++cnt2].P=read(),p[cnt2].col=read();}
	}
	sort(q+1,q+1+cnt1,cmp);
	for(int i=1;i<=cnt1;++i){
		while(L>q[i].L){
			--L;
			++cnt[a[L]];
			if(cnt[a[L]]==1) ++Ans;
		}// add(a[--L]);
		while(L<q[i].L) {
			--cnt[a[L]];
			if(cnt[a[L]]==0) --Ans;
			++L;
		}// del(a[L++]);
		while(R>q[i].R) {
			--cnt[a[R]];
			if(cnt[a[R]]==0) --Ans;
			--R;
		}// del(a[R--]);
		while(R<q[i].R) {
			++R;
			++cnt[a[R]];
			if(cnt[a[R]]==1) ++Ans;
		}// add(a[++R]);
		while(now<q[i].t) modify(i,++now);
		while(now>q[i].t) modify(i,now--);
		ans[q[i].id]=Ans;
	}
	for(int i=1;i<=cnt1;++i){cout<<ans[i]<<endl;}
	return 0;
}
  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值