1. 程式人生 > 實用技巧 >帶修/線上/樹上莫隊

帶修/線上/樹上莫隊

帶修莫隊

往常的莫隊都是用一個 \(l,r\) 來標識當前的狀態,然後每次將這個 \([l,r]\) 的區間不斷擴充套件、縮小
有了修改,那麼可以再增減一維,變成 \([l,r,time]\)\(time\) 可以理解為代表了某個時間的陣列的狀態,比如做一次修改以後,就讓時間加一,因為陣列的狀態改變了
轉移的時候,區間的轉移和一般莫隊相類似,時間上的轉移,就是如果這個時間的修改的點在當前的目標區間(就是當前處理的詢問的區間)中,那麼就刪去陣列中的原數,加入修改後的新數,變成新一個狀態
然後陣列也要改(因為 \(time\) 變了,當前的陣列肯定對應當前的 \(time\)),為了下面的操作,新數也要和原數交換(比如這一次是“順向”的進行這個修改,下一次就應該是時間往後退,倒著進行這個修改)

排序方式與塊大小、複雜度
類比普通莫隊,帶修莫隊的排序方式應該為以左端點所在的塊為第一關鍵字,右端點所在的塊為第二關鍵字,時間為第三關鍵字

關於塊大小:這似乎才是莫隊難的地方,先設塊大小為 \(d\),則一共 \(\frac{n}{d}\) 塊,並假設修改查詢操作都和數列長度相同,分別分析 \(l,r,time\) 三個指標,注意下面說的一些情況都是 XX 的塊 沒變,就是他在塊內移動,而不是它本身沒有移動

  • \(l\) 指標,在塊內移動時(這種情況就是因為幾個詢問左端點相同了,那麼按照右端點或時間排序造成的),每次 \(O(d)\),一個 \(n\) 次詢問,那麼就是 \(O(nd)\)
    移動到下一個塊,每次複雜度 \(O(d)\)
    ,一共 \(O(\frac{n}{d})\) 次,則總複雜度 \(O(n)\)
  • \(r\) 指標,當 \(l,r\) 都在同一個塊,和之前相同,也是 \(n\) 次每次 \(O(d)\),一共 \(O(nd)\)
    \(l\) 的塊沒變,\(r\) 的塊改變,移動到下一個塊,單次 \(O(d)\),一共 \(O((\frac{n}{d})^2)\) 次(每次 \(l\) 的塊移動一下,\(r\) 的塊就要回到最左從新移動,所以是塊數的平方),總複雜度 \(O(\frac{n^2}{d})\)
    \(l,r\) 的塊都改變,就是上面說的那種 \(r\) 的塊移回最左邊重新移動的情況,一共塊數次,每次 \(O(n)\)
    ,那麼總複雜度 \(O(\frac{n^2}{d})\)
  • \(time\) 指標,當 \(l,r\) 的塊都不變,\(time\) 因為在這一段使得 \(l,r\) 的塊不變的詢問區間內是排好序的,所以對於這一段詢問區間是 \(O(n)\),那麼一共有 \(d\)

板子,P1903 [國家集訓隊]數顏色 / 維護佇列:https://www.luogu.com.cn/problem/P1903

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define N 133340
struct data{
	int l,r,id,time;
}q[N];
int qtot;
struct CHANGE{
	int pos,x;
}change[N];
int n,m,B;
int block[N],a[N];
int cnt[1000005],ans[N];
inline int cmp(data a,data b){
	if(block[a.l]==block[b.l]) return block[a.r]==block[b.r]?a.time<b.time:block[a.r]<block[b.r];
	else return block[a.l]<block[b.l];
}
int num;
inline void add(int x){if(++cnt[x]==1) num++;}
inline void del(int x){if(!--cnt[x]) num--;}
inline void work(int time,int i){
	if(change[time].pos>=q[i].l&&change[time].pos<=q[i].r){
		add(change[time].x);del(a[change[time].pos]);
	}
	std::swap(change[time].x,a[change[time].pos]);
	//這一次被改掉的顏色,就是下一次呼叫這個函式(引數相同)需要修改成的顏色
}
int main(){
	n=read();m=read();B=std::pow(n,2.0/3);
	for(reg int i=1;i<=n;i++) a[i]=read(),block[i]=block[i-1]+(!((i-1)%B));
	reg int timenow=1;reg char op;
	for(reg int i=1;i<=m;i++){
		op=getchar();
		while(op!='Q'&&op!='R') op=getchar();
		if(op=='Q') q[++qtot].l=read(),q[qtot].r=read(),q[qtot].id=qtot,q[qtot].time=timenow;
		else{
			change[++timenow].pos=read();change[timenow].x=read();
		}
	}
	std::sort(q+1,q+1+qtot,cmp);
	reg int l=1,r=0,time=1;
	for(reg int nexl,nexr,nexT,i=1;i<=qtot;i++){
		nexl=q[i].l;nexr=q[i].r;nexT=q[i].time;
		while(r<nexr) add(a[++r]);
		while(l>nexl) add(a[--l]);
		while(r>nexr) del(a[r--]);
		while(l<nexl) del(a[l++]);
		while(time<nexT) work(++time,i);//先處理完區間,再處理時間
		while(time>nexT) work(time--,i);
		ans[q[i].id]=num;
	}
	for(reg int i=1;i<=qtot;i++) printf("%d\n",ans[i]);
}