莫隊算法

 

介紹


 

 莫隊算法是一個對於區間、樹或其餘結構離線(在線)維護的算法,此算法基於一些基本算法,例如暴力維護,樹狀數組,分塊,最小曼哈頓距離生成樹,對其進行揉合從而產生的一個簡單易懂且短小好寫的算法。此算法在不少狀況下能夠很輕鬆的切掉一些複雜並且難寫的數據結構問題。php

 

莫隊的門檻


 

簡單的說,莫隊算法就是一個優美的暴力算法,假設操做數組的長度爲n。在算法中,咱們把數組分爲sqrt(n)塊,每一塊的大小(長度)爲sqrt(n)算法

MoDui_algorithm's thinking is to sort the inquiries offline and move several pointer to visit the wanted section數組

其餘的介紹已經說的很詳細了,這裏就不多此一舉了,數據結構

更多的信息在代碼裏,because program is the best languageide

莫隊有一個衆所皆知的入門題,想必不少人已經知道了。對!就是小Z的襪子oop

當咱們完成一個詢問時,只要將左右指針移動,並在每一次移動時更新當前的ans學習

這道題裏咱們須要將詢問按照塊來排序(若左指針在同一塊,就按照右指針排序,不然就按照左指針排序)ui

如上圖所示,當咱們已經求過藍色指針範圍內區間的ans,而後想要求紅色指針範圍內的ans,只要將左右指針向要求範圍的方向移動便可this

要注意的是在移動時咱們必須更新ans,使其知足當前的區間spa

這是莫隊的一個靈魂所在,沒了這個操做莫隊就失去了其魅力

手動模擬一下,你就會發現這種方式是多麼的高效(每個指針移動一次都只須要O(1)的時間)

像上圖的那個狀況,咱們只要O(2)的時間,就能夠求出下一個區間的信息

 

也許有的人會問若是沒有排序會怎麼樣。。。若是沒有排序,你能夠自行腦補一下左右指針處處亂跳這般使人焦頭爛額的場面,

O(n^2)的複雜度,仍是洗洗睡了

莫隊算法巧妙地將詢問離線排序,使得其複雜度無比美妙……

這句話出自哪裏咱們暫且不論,可是這句話能夠說是十分精闢的。這也正是莫隊算法從暴力蛻變的第一步,也正是它的魅力所在

莫隊的時間複雜度究竟到了怎樣的一個使人髮指的境界,咱們也能夠手動演算一下,最後求得的複雜度是O(n*sqrt(n))

固然,每一道題的複雜度都是不同的,須要根據不一樣的狀況來判斷

至於解釋就在代碼裏面了

注意:必定要先看題目再看代碼,不然你可能會一臉懵逼

#include<cstdio>
#include<algorithm>
#include<cmath>
#define f(b) for(int i=1;i<=b;i++)
#define ll long long 
using namespace std;const int N=50003;
struct Mo{int l,r,ID;ll A,B;}q[N];//build a Mo_Dui struct 
//l:left point r:right point ID:read_order
ll S(ll x){return x*x;}
ll GCD(ll a,ll b){while(b^=a^=b^=a%=b);return a;}
//work out greatest_common_divisor of two numbers
int n,m,col[N],unit,Be[N];ll sum[N],ans;
//sum is used to mark each color's sum
//ans is used to mark present_block's information
//col is used to mark each position's color
//Be is to mark each color's block

bool cmp(Mo a,Mo b){return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;}
//if their left pointer are in the same block(part),just compare their right pointer to put them in order
//or,just compare their left pointer to put them in order
bool CMP(Mo a,Mo b){return a.ID<b.ID;};
//this compare function's work is to sort the array depends on read_order
void revise(int x,int add){ans=ans-S(sum[col[x]])+S(sum[col[x]]+=add);}
int main(){
    //here ,we divide the array into sqrt(n) blocks(parts),and each block(part)'s size is sqrt(n)
    //if you don't know ,you can return to primary school and have a study(primary school's Olympic Mathematics)
    scanf("%d%d",&n,&m);//read the total_num of socks and the total_num of inquiries
    unit=sqrt(n);//mark down sqrt(n)
    f(n)//make a loop from 1 to n in order to read each sock's color
    scanf("%d",&col[i]),Be[i]=i/unit+1;//read the color and mark it's block(part)
    f(m)scanf("%d%d",&q[i].l,&q[i].r),q[i].ID=i;//read the chosen section and mark its read_order
    sort(q+1,q+m+1,cmp);//sort the inquiries to make them in order(in this way,we can move pointer more easier)
    //one of Mo_Dui_algorithm's features:offline sort optimization
    int l=1,r=0;//initializate left_pointer as 1 and right_pointer as 0
    f(m)//make a loop from 1 to m in order to visit each inquiry
    {while(l<q[i].l)revise(l,-1),l++;//if the left_pointer is smaller than the wanted_left_pointer,just renew the answer and move the left_pointer
         while(l>q[i].l)revise(l-1,1),l--;//if the left_pointer is bigger than the wanted_left_pointer,just renew the answer and move the left_pointer
        while(r<q[i].r)revise(r+1,1),r++;//if the right_pointer is smaller than the wanted_right_pointer,just renew the answer and move the right_pointer
        while(r>q[i].r)revise(r,-1),r--;//if the right_pointer is bigger than the wanted_right_pointer,just renew the answer and move the right_pointer
        if(q[i].l==q[i].r){q[i].A=0;q[i].B=1;continue;}//if the two points are the same,just mark the answer(no solution)and jump to next inquiry
        //or,just means it may has solution 
        q[i].A=ans-(q[i].r-q[i].l+1);//work out the probability_fraction's numerator
        q[i].B=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l);//work out the probability_fraction's denominator
        ll gcd=GCD(q[i].A,q[i].B);q[i].A/=gcd;q[i].B/=gcd;//change the fraction into fraction in lowest terms
    }sort(q+1,q+m+1,CMP);//sort the inquiries in order to put them in read_order
    f(m)printf("%lld/%lld\n",q[i].A,q[i].B);return 0;}//out the answer

 

 什麼?仍是不懂?那就對照着代碼看接下來的講解吧

如圖,當前完成的詢問的區間爲[a,b],下一個詢問的區間爲[p,q],如今保存[a,b]區間內的每一個顏色出現次數的sum[]數組已經準備好,[a,b]區間詢問的答案Ans1已經準備好,怎樣用這些條件求出[p,q]區間詢問的Ans2?

 

 

考慮指針向左或向右移動一個單位,咱們要付出多大的代價才能維護sum[]和Ans(即便得sum[],Ans保存的是當前[l,r]的正確信息)。咱們對圖中l,r的向右移動一格進行分析:

                                    

 

如圖,l指針向右移動一個單位,所形成的後果就是:咱們損失了一個紅色方塊。那麼怎樣維護?美妙地,sum[紅色]減去1。那Ans如何維護?先看分母,分母從n2變成(n-1)2,分子中的其餘顏色對應的部分是不會變的,紅色卻從sum[紅色]2變成(sum[紅色]-1),爲了方便計算咱們能夠直接向給Ans減去之前該顏色的答案貢獻(即sum[紅色]2)再加上如今的答案貢獻(即(sum[紅色]-1))。同理,觀賞下面的r指針移動,將是差很少的。

                                      

 

·如圖r指針的移動帶來的後果是,咱們多了一個藍色方塊。因此操做和上文類似,只不過是sum[藍色]++。

 

進階1:帶修莫隊(可持久化莫隊)


 

話說我第一次接觸的關於莫隊的題目就是帶修莫隊

上面的那句話手動無視掉,開始接下來的講解,帶修莫隊,能夠說在計算機題目中也算比較常見的

因此學習它是很是必要的。

帶修莫隊是什麼?字面上的意思,就是帶修改操做的莫隊算法。

和上面同樣,我先放出例題:數顏色

和上面的小Z的襪子同樣,這道題也是一道衆所皆知的莫隊題目,由於題目的要求十分直觀,就用它做爲帶修莫隊的入門題

 這道題咱們須要用到一個新的指針:時間指針,用來記錄當前詢問是在第幾回修改以後的

放出代碼,對照題目欣賞美妙的實現過程

 

#include<cstdio>
#include<algorithm>
#include<cmath>
#define f(b) for(int i=1;i<=b;i++)
using namespace std;const int N=10003;
struct Query{int l,r,Tim,ID;}q[N];
//l: inquiry_section's left_border  r: inquiry_section's right_border  
//Tim :this inquiry's change_times  ID:inquiry_order
struct Change{int pos,New,Old;}c[N];//the highlight part 1!!   persistent-marked array
//pos: the position changed 
//New: this position's new color
//Old: this position's previous color
int n,m,s[N],color[N*100],Time,t,now[N],unit,Be[N],ans[N],Ans,l=1,r,T;
//s[]:each colorpen's present_color depends on read_order
//Be[]:mark each location's block
//now[]:mark each location's present color
//unit:the size(length) of each block 
//ans[]:against each inquiry,mark it's answer
//l: present left_pointer r:present right_pointer Ans:present answer   T:present time
//n:total_number of colorpens m:total_number of inquiries   t,Time:iterator
//color:marked each color's number
bool cmp(Query a,Query b){return Be[a.l]==Be[b.l]?(Be[a.r]==Be[b.r]?a.Tim<b.Tim:a.r<b.r):a.l<b.l;}
//if their left_pointers are in the same block just compare their right_pointers' block,
//                         if their right_pointers are in the same block ,just sort them depends on change_times,
//                          or just sort depends on their right_pointer
//if their left_pointers are not in the same block,just sort depends on their left_pointers
void revise(int x,int d){color[x]+=d;if(d>0)Ans+=color[x]==1;if(d<0)Ans-=color[x]==0;}//the highlight part 2!!   revise_operation
//change this color's number ,if have add this color's num:if color[x]!=1,just means it has been add into the Ans or just means has not and add it into the Ans
//if have minus this color's num: if color[x]!=0,just means this color is still real inside the present_inquiry_section.Or ,just means this color isn't real inside the present_inquiry_section,just renew the Ans
void going(int x,int d){if(l<=x&&x<=r)revise(d,1),revise(s[x],-1);s[x]=d;}
//if x is inside the section ,just operate on it:add the new color and minus the previous color
//at last change the  colorpen's color
int main(){scanf("%d%d",&n,&m);//read the total_number of colorpens and the total_number of inquiries 
    unit=pow(n,0.666666);//set the size of each block 
    f(n)scanf("%d",&s[i]),now[i]=s[i],Be[i]=i/unit+1;//make a loop from 1 to n in order to read each colorpen's color
    //mark this location's color,and its block
    f(m){char sign;int x,y;scanf(" %c %d%d",&sign,&x,&y);//read the kind of inquiry,x,and y
        if(sign=='Q')q[++t]=(Query){x,y,Time,t};//if the kind is Q,just means it asked for different_colorpens_num between x and y
        //just push it into the query_array
        else c[++Time]=(Change){x,y,now[x]},now[x]=y;//or ,just the kind is R,it wanted to revise x's color into y
        //just pish it into the change_array,and change this location's color
    }
    sort(q+1,q+t+1,cmp);//sort the query_array depends on incremental order
    f(t){//make a loop from 1 to t in order to deal with each inquiry
        while(T<q[i].Tim)going(c[T+1].pos,c[T+1].New),T++;
        //if the now_time is smaller than the wanted time ,just change the color into the present_time's color and renew the time
        while(T>q[i].Tim)going(c[T].pos,c[T].Old),T--;
        //if the now_time is larger than the wanted time , just change the color into the previous_time's color and renew the time
        while(l<q[i].l)revise(s[l],-1),l++;
        //if the present left_pointer is smaller than the wanted left_pointer ,just move the left_pointer to right,renew the Ans and the present_color_number
        while(l>q[i].l)revise(s[l-1],1),l--;
        //if the present left_pointer is larger than the wanted left_pointer ,just move the left_pointer to left ,renew the Ans and the present_color_number
        while(r<q[i].r)revise(s[r+1],1),r++;
        //if the present right_pointer is smaller than the wanted right_pointer ,just move the right_pointer to right ,renew the Ans and the present_color_number
        while(r>q[i].r)revise(s[r],-1),r--;
        //if the present right_pointer is larger than the wanted right_pointer ,just move the right_pointer to left ,renew the Ans and the present_color_number
        ans[q[i].ID]=Ans;//mark the answer depends on inquiry_order
        }f(t)printf("%d\n",ans[i]);return 0;}//out the answer

 時間複雜度爲O(unit*n+n2/unit+(n/unit)2*n),仍是很可觀的QAQ

至於過程,本質性的和普通莫隊差很少,至於詳細過程,就觀摩上面的程序吧

 

進階2:樹上帶修莫隊


 

 

雖然以爲有了上面的講解,樹上帶修莫隊也不算問題,但由於這一類題目比較典型,仍是仁慈地講一講

無視上面的那句話,進入本篇博文目前的最終章——樹上帶修莫隊

樹上帶修莫隊是什麼?字面上的意思,在一棵樹上進行帶修莫隊

實現的過程和帶修莫隊並無什麼區別

好吧,仍是有一些區別的,首先是分塊的問題,不少人可能會問,那麼大一坨樹,該怎麼對它進行分塊呢?

這一點初學者一臉懵逼也是很正常的,畢竟樹上莫隊不比普通莫隊那麼直觀,只要在一條線上胡亂分塊便可

而樹有分支,每一棵子樹的狀況也不盡相同。不少人聽到這個名字就會感到五雷轟頂之感。

可是,我在這裏負責任地告訴你,樹上帶修莫隊也一樣簡單

依舊是熟悉的配方,按題目狀況分unit

分塊的目的是爲了快速訪問與查找

這句話是一位巨神說的。確實這樣,學習一個算法的過程,就是由淺入深,由深刻淺的過程。

學了這麼多,咱們又回到了算法的本質,也正是該算法的靈魂。分塊是爲了什麼?就是爲了快速訪問和查找啊!

一樣的步驟,將一棵樹分紅若干塊,每一塊的大小<=unit

嘗試在樹上構造相鄰的塊,使得:塊內元素的互相訪問的移動次數控制在一個範圍內(也就是unit)。作法是用棧維護當前節點做爲父節點訪問它的子節點,當從棧頂到父節點的距離大於unit時,彈出這部分元素分爲一塊。

 

如圖,相同顏色的分爲一塊。另外,對於剩餘分塊的節點,也就是根節點附近因爲個數小於unit而造成的一坨點,最後再分一塊或加在最後一塊中

在該圖中根節點爲剩餘分塊的節點,它單獨分一塊

這樣分有什麼好處呢?這樣分可使每個塊內的點到達另外一個點最多移動unit次(具體看普通莫隊)

解決了分塊的問題,另一個問題也接踵而至——樹上指針移動問題。

又是給新學者的一個沉重打擊,可是方法也一樣簡單,樹是彎的怎麼辦?把彎的掰直啊!

試想一下,從現指針到目標指針的路徑,本質上就是一條線,線上的指針移動,其實跟普通莫隊差很少,只不過須要遵循一些樹的規則罷了

多說無益,入門題奉上:Haruna’s Breakfast

在訪問時,咱們用vis[u]來記錄u是否被訪問過

 如何進行維護呢?常見的維護方法是離散化加數據結構。其實也沒有什麼定性要求,用分塊,樹狀數組仍是其它的玄學數據結構全憑我的的喜愛

先放出代碼一睹爲快吧

void ADD(int from,int to){e[k]=(E){to,head[from]};head[from]=k++;}
void dfs(int u){fr(i,1,19)if((1<<i)>deep[u])break;else fa[u][i]=fa[fa[u][i-1]][i-1];
    int bottom=top;
    tf(i,head,u)if(to!=fa[u][0]){fa[to][0]=u;deep[to]=deep[u]+1;dfs(to);
        if(top-bottom>=unit){m++;while(top!=bottom)Be[st[top--]]=m;}}st[++top]=u;}
int LCA(int x,int y){if(deep[x]<deep[y])swap(x,y);int Dis=deep[x]-deep[y];
    fr(i,0,16)if((1<<i)&Dis)x=fa[x][i];if(x==y)return x;
    fd(i,16,0)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];return x==y?x:fa[x][0];}
struct Change{int u,New,Old;}cq[N];
struct Query{int u,v,tim,id;
   bool operator <(const Query &a) const{return Be[u]==Be[a.u]?(Be[v]==Be[a.v]?tim<a.tim:Be[v]<Be[a.v]):Be[u]<Be[a.u];}}q[N];
struct Datalock{struct _blo{int l,r;}b[350];
    int n,Be[N],m,unit,num[N],sum[350];
    void init(){unit=sqrt(n);m=(n-1)/unit+1;fr(i,1,n)Be[i]=(i-1)/unit+1;
        fr(i,1,m)b[i].l=(i-1)*unit+1,b[i].r=i*unit;b[m].r=n;}
    void Add(int v){if(v<=n)sum[Be[v]]+=(++num[v])==1;}
    void Del(int v){if(v<=n)sum[Be[v]]-=(--num[v])==0;}
    int mex(){fr(i,1,m)if(sum[i]!=b[i].r-b[i].l+1)
        fr(j,b[i].l,b[i].r)if(!num[j])return j;return -1;}}Data;
void revise(int u,int d){if(vis[u])Data.Del(a[u]),Data.Add(d);a[u]=d;}
void Run(int u){if(vis[u])Data.Del(a[u]),vis[u]=0;else Data.Add(a[u]),vis[u]=1;}
void move(int x,int y){ if(deep[x]<deep[y])swap(x,y);
    while(deep[x]>deep[y])Run(x),x=fa[x][0];while(x!=y)Run(x),Run(y),x=fa[x][0],y=fa[y][0];}
void Mo(){fr(i,1,p){
        while(T<q[i].tim)T++,revise(cq[T].u,cq[T].New);
        while(T>q[i].tim)revise(cq[T].u,cq[T].Old),T--;
        if(u!=q[i].u)move(u,q[i].u),u=q[i].u;if(v!=q[i].v)move(v,q[i].v),v=q[i].v;
        int anc=LCA(u,v);Run(anc);ans[q[i].id]=Data.mex()-1;Run(anc);}}

其中有不少部分都是和帶修莫隊差很少

你看到了什麼,LCA???,對的,這也是樹上莫隊和普通莫隊的區別,除了訪問數組,還要處理到達公共祖先的特殊狀況

爲何會有特殊狀況?讓我來手動模擬一遍來解釋它

 

如圖所示狀況

u,v兩個指針在通過根節點以前都沒有發生什麼怪異狀況,

可是當它們通過u*,v*(即當前查詢的區間兩指針)的最近公共祖先節點(橙色強調出來的慘案現場)時

 

void Run(int u){if(vis[u])Data.Del(a[u]),vis[u]=0;else Data.Add(a[u]),vis[u]=1;}

 

顯而易見,它被標註了兩次,這意味着它被設爲沒訪問過

沒有加上LCA的程序的BUG正是在此,而這顯然不是咱們想要看到的

這就是LCA的用處了,它打破了重複標註的怪圈,使程序從新變得和平

除此以外還要強調一點,上面所說的維護的數據結構沒有維護LCA點的信息

每次u,v歸位後,咱們單獨爲LCA計算一次,這樣既避免了怪異狀況影響答案,有保證了LCA對答案的貢獻。

接下來是詳細代碼。接下來的一幕可能會傷害到你幼小的心靈,

但我已經竭盡所能碼的最明瞭了QAQ。。。。。。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define ct    register int
#define ff(b)   for(ct i=1;i<=b;i++)
#define fr(i,a,b) for(ct i=a;i<=b;i++)
#define fd(i,a,b) for(ct i=a;i>=b;i--)
#define tf(i,a,x) for(ct i=a[x],to=e[i].to;i;i=e[i].next,to=e[i].to)
int in(){ct x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}return x*f;}
using namespace std; 




/*--global variables--*/
const int N=50009; 
int unit,Be[N],m,ans[N],vis[N];
//unit:the size(length) of each block 
//Be[]:mark each location's block
//m:total_number of inquiries 
//ans[]:against each inquiry,mark it's answer
//vis[]:mark each endpoint if it has been visited
int st[N],top;//stack
int fa[N][18],deep[N];
//fa[][]:each endpoint's father
//deep: each endpoint's deep
int a[N],u=1,v=1,T;
//T,u,v:iterator
//a[]:each point's delicious_num
int tim,p;//counters
/*--global variables above--*/



/*--edge--*/
int cnt=1,head[N]; 
struct E{int to,next;}e[N*3];//edge
//v:to_endpoint  next:from_endpointer's point
void ADD(int from,int to){e[cnt]=(E){to,head[from]};head[from]=cnt++;}//add edge
/*--edge above--*/ 




/*--Mo struct--*/
struct Change{int u,New,Old;}cq[N];//persistent-marked array
//pos: the position changed 
//New: this position's new color
//Old: this position's previous color
struct Query{int u,v,tim,id;
   //u: inquiry_section's left_border  v: inquiry_section's right_border  
   //Tim :this inquiry's change_times  id:inquiry_order
   bool operator <(const Query &a) const{return Be[u]==Be[a.u]?(Be[v]==Be[a.v]?tim<a.tim:Be[v]<Be[a.v]):Be[u]<Be[a.u];}}q[N];
//encapsulate the block_struct,set compare_function inside
/*--Mo struct above--*/




//---------
struct Datalock{
    struct _blo{int l,r;}b[350];//pointer
    //l: present left_pointer r:present right_pointer 
    int n,Be[N],m,unit,num[N],sum[350];
    //n:total_number of colorpens 
    //Be[]:mark each location's block
    //m:total_number of inquiries t,Time:iterator
    //unit:the size(length) of each block 
    //num:marked each color's number
    void init(){unit=sqrt(n);m=(n-1)/unit+1;ff(n)Be[i]=(i-1)/unit+1;
       ff(m)b[i].l=(i-1)*unit+1,b[i].r=i*unit;b[m].r=n;}
    void Add(int v){if(v<=n)sum[Be[v]]+=(++num[v])==1;}
    void Del(int v){if(v<=n)sum[Be[v]]-=(--num[v])==0;}
    int mex(){ff(m)if(sum[i]!=b[i].r-b[i].l+1)
        fr(j,b[i].l,b[i].r)if(!num[j])return j;return -1;}}Data;
//----------encapsulate the revise_operation





void dfs(int u){ff(19)if((1<<i)>deep[u])break;else fa[u][i]=fa[fa[u][i-1]][i-1];
    int bottom=top;
    tf(i,head,u)if(to!=fa[u][0]){fa[to][0]=u;deep[to]=deep[u]+1;dfs(to);
        if(top-bottom>=unit){m++;while(top!=bottom)Be[st[top--]]=m;}}st[++top]=u;}
int LCA(int x,int y){if(deep[x]<deep[y])swap(x,y);int Dis=deep[x]-deep[y];
    fr(i,0,16)if((1<<i)&Dis)x=fa[x][i];if(x==y)return x;
    fd(i,16,0)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];return x==y?x:fa[x][0];}
void revise(int u,int d){if(vis[u])Data.Del(a[u]),Data.Add(d);a[u]=d;}
//revise_operation:delt the old_num's message and add the new_num's message,at last change the num
void Run(int u){if(vis[u])Data.Del(a[u]),vis[u]=0;else Data.Add(a[u]),vis[u]=1;}
//visit operation:if u has been visited,just delt its message,and mark it has not been visited
//or,just add its message and mark it has been visited
void move(int x,int y){if(deep[x]<deep[y])swap(x,y);
    while(deep[x]>deep[y])Run(x),x=fa[x][0];while(x!=y)Run(x),Run(y),x=fa[x][0],y=fa[y][0];}  
//move operation:move from the deeper one to another one ,until they are in the same deep
//then there are two cases:
//case 1:they are same,just means has moved from x to y,just return
//case 2:they are different,just means they are in the different side of their LCA,just move them bove,until they meet
        
        
        

int main(){int uu,vv,op,x,y,n,Q,t[N];
    scanf("%d%d",&n,&Q);//read the total_number of points and the total_number of operations
    unit=pow(n,0.45);//work out the unit
    ff(n)a[i]=in(),t[i]=++a[i];//read each point's delicious_num
    ff(n-1)uu=in(),vv=in(),ADD(uu,vv),ADD(vv,uu);//read each edge,add this edge
    dfs(1);//initialization
    while(top)Be[st[top--]]=m;//if some points don't have its block,just set them together as the last block
    ff(Q){op=in(),x=in(),y=in();//read the operation and two num
        if(op)p++,q[p]=(Query){x,y,tim,p};//query_operation,just want present smallest absent_natural_number
        //just push it into to query_array
        else tim++,cq[tim]=(Change){x,y+1,t[x]},t[x]=y+1;//revise_operation,just push it into the change_array and change it
    }Data.n=n+1;Data.init();//initialization
    sort(q+1,q+1+p);//sort the query_array depends on incremental order
    ff(p){//make a loop from 1 to p in order to deal with each inquiry
        while(T<q[i].tim)T++,revise(cq[T].u,cq[T].New);
        //if the now_time is smaller than the wanted time ,just change the num into the present_time's num and renew the time
        while(T>q[i].tim)revise(cq[T].u,cq[T].Old),T--;
        //if the now_time is larger than the wanted time , just change the color into the previous_time's color and renew the time
        if(u!=q[i].u)move(u,q[i].u),u=q[i].u;if(v!=q[i].v)move(v,q[i].v),v=q[i].v;
        //if the now_left_pointer and now_left_pointer are not in the wanted position,just move them
        int anc=LCA(u,v);Run(anc);ans[q[i].id]=Data.mex()-1;Run(anc);
        //extra operation:calculate out the LCA's message
    }ff(p)printf("%d\n",ans[i]);//out the answer
}

對於三個常見莫隊題型已經講解完畢,以後可能會補上一些莫隊題目,總之,未完待續

相關文章
相關標籤/搜索