帶修/線上/樹上莫隊
阿新 • • 發佈:2020-08-21
帶修莫隊
往常的莫隊都是用一個 \(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)\) - \(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)\) - \(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]);
}