莫隊實際很簡(du)單(liu)
依照某位dalao的說法,就是兩隻小手(two-pointers)瞎跳
一.莫隊(靜態莫隊)
這道題是個綠題,由於數據比較弱,但真是一道良心的莫隊練手題
莫隊是由前國家隊隊長莫濤發明的
莫隊算法的精髓就是經過合理地對詢問排序,而後以較優的順序暴力回答每一個詢問。處理完一個詢問後,可使用它的信息獲得下一個詢問區間的答案。(兩個小手瞎跳)
考慮這個問題:對於上面這道題,咱們知道區間[1,5]每一個數的數量,如何求出[2,6]每一個數的數量
算法1:暴力掃一遍(廢話)
算法2:珂以在區間[1,5]的基礎上,去掉位置1(即將左端點右移一位),加上位置6(即將右端點右移一位),獲得區間[2,6]的答案。
若是按這樣寫,一種很簡單的構造數據就能把時間複雜度把算法2也送上天:先詢問[1,2],再詢問[99999,100000],多重複幾回就gg
但莫隊算法是算法2的改進版
要進行合理的排序,使得每兩個區間的距離最小
但如何進行合理的排序?
莫隊提供了這樣一個排序方案:將原序列以$ \sqrt n$爲一塊進行分塊(分塊的大小也珂以調整),排序第一關鍵字是詢問的左端點所在塊的編號,第二關鍵字是詢問的右端點自己的位置,都是升序。而後咱們用上面提到的「移動當前區間左右端點」的方法,按順序求每一個詢問區間的答案,移動每個詢問區間左右端點能夠求出下一個區間的答案。
這就是通常的莫隊排序
inline bool cmp(register query a,register query b)
{
return a.bl==b.bl?a.r<b.r:a.bl<b.bl;
}
但因爲出題人過於毒瘤
又多出一種優化,叫作奇偶優化
按奇偶塊排序。這也是比較通用的。若是區間左端點所在塊不一樣,那麼就直接按左端點從小到大排;若是相同,奇塊按右端點從小到大排,偶塊按右端點從大到小排。
inline bool cmp(register query a,register query b)
{
return a.bl!=b.bl?a.l<b.l:((a.bl&1)?a.r<b.r:a.r>b.r);
}
莫隊核心代碼qaq:
sort(q+1,q+m+1,cmp); //講詢問按上述方法排序
int l=1,r=0; //當前左端點和右端點初值(兩隻小手two-pointers)
for(register int i=1;i<=m;++i) //對排序後的詢問一個個轉移
{
int ll=q[i].l,rr=q[i].r; //本次詢問的區間
//轉移,++--這些東西比較容易寫錯,須要注意
while(l<ll)
del(l++);
while(l>ll)
add(--l);
while(r<rr)
add(++r);
while(r>rr)
del(r--);
ans[q[i].id]=sth; //詢問是排過序的,存到答案數組裏須要返回原順序
}
這樣就能夠求出答案了!
——但是,這樣作的複雜度是什麼?
大約是\(O(n \sqrt n)\)
Luogu P3901 AC代碼:
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define N 100005
using namespace std;
inline int read()
{
register int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
int v[N],blocksize=0;
struct query{
int l,r,id,bl;
}q[N];
int sum[N];
bool ans[N];
int cnt=0;
inline void add(register int x)
{
if(++sum[v[x]]==1)
++cnt;
}
inline void del(register int x)
{
if(--sum[v[x]]==0)
--cnt;
}
inline bool cmp(register query a,register query b)
{
return a.bl!=b.bl?a.l<b.l:((a.bl&1)?a.r<b.r:a.r>b.r);
}
int main()
{
memset(sum,0,sizeof(sum));
int n=read(),m=read();
blocksize=sqrt(n);
for(register int i=1;i<=n;++i)
v[i]=read();
for(register int i=1;i<=m;++i)
{
int l=read(),r=read();
q[i]=(query){l,r,i,(l-1)/blocksize+1};
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(register int i=1;i<=m;++i)
{
int ll=q[i].l,rr=q[i].r;
while(l<ll)
del(l++);
while(l>ll)
add(--l);
while(r<rr)
add(++r);
while(r>rr)
del(r--);
ans[q[i].id]=(cnt==rr-ll+1)?1:0;
}
for(register int i=1;i<=m;++i)
if(ans[i])
puts("Yes");
else
puts("No");
return 0;
}
例題:
二.動態莫隊(單點修改)
寫完了上面這道題,能夠發現:普通的莫隊算法沒有支持修改。那麼如何改造該算法使它支持修改呢?
莫隊俗稱優雅的暴力
那麼咱們改造莫隊算法的思路也只有一個:改造詢問排序的方式,而後繼續暴力。
首先咱們須要把查詢操做和修改操做分別記錄下來。
在記錄查詢操做的時候,須要增長一個變量來記錄離本次查詢最近的修改的位置
而後套上莫隊的板子,與普通莫隊不同的是,你須要用一個變量記錄當前已經進行了幾回修改
每次回答詢問時,先從上一個詢問的時間「穿越」到當前詢問的時間:若是當前詢問的時間更靠後,則順序執行全部修改,直到達到當前詢問時間;若是當前詢問的時間更靠前,則「時光倒流」,還原全部多餘的修改。進行推移時間的操做時,若是涉及到當前區間內的位置的修改,要對答案進行相應的維護。
排序有三個關鍵字:
1.左端點所在塊數 2.右端點所在塊數 3.在此次修改以前查詢的次數
inline bool cmp(register query a,register query b)
{
return a.bll!=b.bll?a.bll<b.bll:(a.blr!=b.blr?a.blr<b.blr:a.pre<b.pre);
}
完整代碼,代碼中有詳細註釋
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define N 50005
using namespace std;
inline int read()
{
register int x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register int x)
{
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[25];int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
struct change{ //記錄修改操做的結構體,place爲修改的位置,pre是修改以前的值,suf是修改以後的值
int place,pre,suf;
}cg[N];
struct query{ //記錄查詢操做的結構體,l,r爲查詢左右端點,pre表示以前有過幾回修改,id表示這是第幾回查詢,bll,blr表示左右端點所在的塊
int l,r,pre,id,bll,blr;
}q[N];
int a[N],blocksize=0,p[1000001],ans[N];
inline bool cmp(register query a,register query b) //按上述三個關鍵字排序
{
return a.bll!=b.bll?a.bll<b.bll:(a.blr!=b.blr?a.blr<b.blr:a.pre<b.pre);
}
int main()
{
int n=read(),m=read(),tota=0,totb=0;
for(register int i=1;i<=n;++i)
a[i]=read();
for(register int i=1;i<=m;++i)
{
char ch=getchar();
while(ch!='R'&&ch!='Q')
ch=getchar();
if(ch=='R') //修改
{
cg[++tota].place=read(),cg[tota].suf=read();
cg[tota].pre=a[cg[tota].place]; //爲了方便先在原數組上修改
a[cg[tota].place]=cg[tota].suf;
}
else
{
int l=read(),r=read();
q[++totb]=(query){l,r,tota,totb,0};
}
}
blocksize=ceil(exp((log(n)+log(tota))/3)); //奇妙的塊的大小
for(register int i=1;i<=totb;++i)
q[i].bll=(q[i].l-1)/blocksize+1,q[i].blr=(q[i].r-1)/blocksize+1;
for(register int i=tota;i>=1;--i) //還原數組
a[cg[i].place]=cg[i].pre;
sort(q+1,q+totb+1,cmp); //排序
int l=1,r=0,num=0,ti=0;
for(register int i=1;i<=m;++i)
{
int ll=q[i].l,rr=q[i].r,t=q[i].pre;
//正常莫隊操做
while(ll<l)
num+=!p[a[--l]]++;
while(ll>l)
num-=!--p[a[l++]];
while(rr>r)
num+=!p[a[++r]]++;
while(rr<r)
num-=!--p[a[r--]];
while(t<ti) //當本次查詢時修改的次數小於已經修改的次數,時光倒流 (還原修改)
{
int pla=cg[ti].place;
if(l<=pla&&pla<=r)
num-=!--p[a[pla]];
a[pla]=cg[ti--].pre;
if(l<=pla&&pla<=r)
num+=!p[a[pla]]++;
}
while(t>ti) //當本次查詢時修改的次數大於已經修改的次數,穿越 (把該修改的修改)
{
int pla=cg[++ti].place;
if(l<=pla&&pla<=r)
num-=!--p[a[pla]];
a[pla]=cg[ti].suf;
if(l<=pla&&pla<=r)
num+=!p[a[pla]]++;
}
ans[q[i].id]=num;
}
for(register int i=1;i<=totb;++i)
{
write(ans[i]);
printf("\n");
}
return 0;
}
3、樹上莫隊
樹上莫隊,顧名思義就是把莫隊搬到樹上。
複雜度同序列上的莫隊(不帶修:\(O(n \sqrt n)\),帶修:\(O(n^\frac{5}{3})\))
題目意思很明確:給定一個n個節點的樹,每一個節點表示一個整數,問u到v的路徑上有多少個不一樣的整數。
像這種不帶修改數顏色的題首先想到的確定是樹套樹莫隊,那麼如何把在序列上的莫隊搬到樹上呢?
歐拉序
咱們考慮用什麼東西能夠把樹上的問題轉化到序列上,dfs序是能夠的,可是這道題不行(沒法搞lca的貢獻)
有一種神奇的東西,叫作歐拉序。
它的核心思想是:當訪問到點i時,加入序列,而後訪問i的子樹,當訪問完時,再把i加入序列
煮個栗子,下面這棵樹的歐拉序爲
1 2 3 4 4 5 5 6 6 3 7 7 2 1

有了這個有什麼用呢?
咱們考慮咱們要解決的問題:求x到y的路徑上有多少個不一樣的整數
這裏咱們設st[i]表示訪問到i時加入歐拉序的時間,ed[i]表示回溯通過i時加入歐拉序的時間
不妨設st[x]<st[y](也就是先訪問x,再訪問y)
分狀況討論
若lca(x,y)=x,這時x,y在一條鏈上,那麼st[x]到st[y]這段區間中,有的點出現了兩次,有的點沒有出現過,這些點都是對答案沒有貢獻的,咱們只須要統計出現過1次的點就好
好比當詢問爲2,6時,(st[2],st[6])=2 3 4 4 5 5 6 4,5這兩個點都出現了兩次,所以不統計進入答案
若lca(x,y)≠x,此時x,y位於不一樣的子樹內,咱們只須要按照上面的方法統計ed[x]到st[y]這段區間內的點。
好比當詢問爲4,7時,(ed[4],st[7])=4 5 5 6 6 3 7。你們發現了什麼?沒錯!咱們沒有統計lca,所以咱們須要特判lca
算歐拉序以後能夠順帶重鏈剖分,這樣lca就直接樹剖來求了qaq
完整代碼
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define N 40005
#define M 100005
#define getchar nc
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
register int x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register int x)
{
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[20];register int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
inline void Swap(register int &a,register int &b)
{
a^=b^=a^=b;
}
struct edge{
int to,next;
}e[N<<1];
int head[N],cnt=0;
inline void add(register int u,register int v)
{
e[++cnt]=(edge){v,head[u]};
head[u]=cnt;
}
int n,m;
int a[N],date[N];
int dep[N],top[N],siz[N],son[N],fa[N],st[N],ed[N],pot[N<<1],tot=0;
inline void dfs1(register int x,register int f)
{
fa[x]=f,siz[x]=1,st[x]=++tot;
pot[tot]=x;
for(register int i=head[x];i;i=e[i].next)
if(e[i].to!=f)
{
dep[e[i].to]=dep[x]+1;
dfs1(e[i].to,x);
siz[x]+=siz[e[i].to];
if(siz[e[i].to]>siz[son[x]])
son[x]=e[i].to;
}
ed[x]=++tot;
pot[tot]=x;
}
inline void dfs2(register int x,register int topf)
{
top[x]=topf;
if(son[x])
dfs2(son[x],topf);
for(register int i=head[x];i;i=e[i].next)
if(e[i].to!=fa[x]&&e[i].to!=son[x])
dfs2(e[i].to,e[i].to);
}
inline int Getlca(register int x,register int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
Swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
struct query{
int l,r,id,lca;
}q[M];
int bel[N<<1],block;
inline bool cmp(register query a,register query b)
{
return bel[a.l]!=bel[b.l]?a.l<b.l:((bel[a.l]&1)?a.r<b.r:a.r>b.r);
}
int ans[M],p[N],vis[N],res=0;
inline void add(register int x)
{
res+=!p[x]++;
}
inline void del(register int x)
{
res-=!--p[x];
}
inline void Add(register int x)
{
vis[x]?del(a[x]):add(a[x]);
vis[x]^=1;
}
int main()
{
n=read(),m=read();
block=sqrt(n);
for(register int i=1;i<=n;++i)
a[i]=date[i]=read();
for(register int i=1;i<=(n<<1);++i)
bel[i]=i/block+1;
sort(date+1,date+1+n);
int num=unique(date+1,date+1+n)-date-1;
for(register int i=1;i<=n;++i)
a[i]=lower_bound(date+1,date+1+num,a[i])-date;
for(register int i=1;i<n;++i)
{
int u=read(),v=read();
add(u,v),add(v,u);
}
dep[1]=1;
dfs1(1,0);
dfs2(1,1);
for(register int i=1;i<=m;++i)
{
int x=read(),y=read();
if(st[x]>st[y])
Swap(x,y);
int lcaa=Getlca(x,y);
q[i].id=i;
if(lcaa==x)
q[i].l=st[x],q[i].r=st[y];
else
q[i].l=ed[x],q[i].r=st[y],q[i].lca=lcaa;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(register int i=1;i<=m;++i)
{
int ll=q[i].l,rr=q[i].r;
while(l<ll)
Add(pot[l++]);
while(l>ll)
Add(pot[--l]);
while(r<rr)
Add(pot[++r]);
while(r>rr)
Add(pot[r--]);
if(q[i].lca)
Add(q[i].lca);
ans[q[i].id]=res;
if(q[i].lca)
Add(q[i].lca);
}
for(register int i=1;i<=m;++i)
write(ans[i]),puts("");
return 0;
}
4、回滾莫隊
1.咱們以塊編號爲第一關鍵字排序,右端點位置爲第二關鍵字排序
2.詢問時依次枚舉區間,咱們保留右端點的移量(右邊單增),左端點則每次在這一個塊中來回移動
3.下一個塊時,清空統計答案重作
因此對於每個塊:左端點每次操做\(O(\sqrt n)\),右端點總共移n,均攤\(O(\sqrt n)\),所以時間複雜度保證了\(O(n\sqrt n)\)
#include <bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
inline ll read()
{
register ll x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register ll x)
{
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[36];int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
inline ll Max(register ll a,register ll b)
{
return a>b?a:b;
}
struct query{
int l,r,id,bll,blr;
}q[N];
inline bool cmp(register query a,register query b)
{
return a.bll==b.bll?a.r<b.r:a.bll<b.bll;
}
int n,m,blocksize;
int a[N],v[N];
ll sum[N],num[N],ans[N];
ll pre,now;
inline void add(register int x)
{
sum[x]+=v[x];
now=Max(now,sum[x]);
}
inline void del(register int x)
{
sum[x]-=v[x];
}
int main()
{
n=read(),m=read();
blocksize=sqrt(n);
for(register int i=1;i<=n;++i)
v[i]=a[i]=read();
sort(v+1,v+1+n);
int tot=unique(v+1,v+1+n)-v-1;
for(register int i=1;i<=n;++i)
a[i]=lower_bound(v+1,v+1+tot,a[i])-v;
for(register int i=1;i<=m;++i)
{
int l=read(),r=read();
q[i]=(query){l,r,i,(l-1)/blocksize+1,(r-1)/blocksize+1};
}
sort(q+1,q+1+m,cmp);
int l=1,r=0,pos=0;
for(register int i=1;i<=m;++i)
{
int ql=q[i].l,qr=q[i].r;
if(q[i].bll!=q[i-1].bll)
{
memset(sum,0,sizeof(sum));
pre=now=0;
l=pos=q[i].bll*blocksize+1;
r=l-1;
}
if(q[i].bll==q[i].blr)
{
ll cur=0;
for(register int j=ql;j<=qr;++j)
{
num[a[j]]+=v[a[j]];
cur=Max(cur,num[a[j]]);
}
for(register int j=ql;j<=qr;++j)
num[a[j]]-=v[a[j]];
ans[q[i].id]=cur;
continue;
}
while(r<qr)
add(a[++r]);
pre=now;
while(l>ql)
add(a[--l]);
ans[q[i].id]=now;
while(l<pos)
del(a[l++]);
now=pre;
}
for(register int i=1;i<=m;++i)
write(ans[i]),puts("");
return 0;
}