樹上莫隊演算法

2021-06-27 05:47:06 字數 4095 閱讀 7137

繼續回來寫部落格……記錄點有意思的題目什麼的。

貌似寫過這個的沒多少人……所以我也記錄一點。

首先序列上的莫隊大家都應該很熟悉了……

那麼樹上的莫隊要怎麼搞呢?

先來看個題目……spoj cot2:求樹上兩點間路徑上有多少個不同的點權。

序列上的莫隊是把詢問按照左端點分塊了……可是樹上沒有左端點,怎麼辦呢?我們把樹分塊。

按照dfs時間戳順序,將樹分成o(sqrt(n))個大小為o(sqrt(n))的塊,那麼樹上的莫隊詢問排序的第一關鍵字就是第乙個節點所在的塊了!

這樣分塊以後,任意兩個塊之間的距離也是o(sqrt(n))級別的,所以時間複雜度是***的。

第二個關鍵字自然就是節點的dfs時間戳了!

但是,還有乙個問題。樹上的區間要怎麼轉移呢?要怎麼從乙個區間變到另乙個區間呢?

這就有些難了,因為樹上有lca,貌似不好處理。

orz了wyfcyx後,找到了vfk的部落格看了一下。

「用s(v, u)代表 v到u的路徑上的結點的集合。

用root來代表根結點,用lca(v, u)來代表v、u的最近公共祖先。

那麼s(v, u) = s(root, v) xor s(root, u) xor lca(v, u)

其中xor是集合的對稱差。

簡單來說就是

節點出現兩次消掉。

lca很討厭,於是再定義

t(v, u) = s(root, v) xor s(root, u)

觀察將curv移動到targetv前後t(curv, curu)變化:

t(curv, curu) = s(root, curv) xor s(root, curu)

t(targetv, curu) = s(root, targetv) xor s(root, curu)

取對稱差:

t(curv, curu) xor 

t(targetv, curu)

= (s(root, curv) xor s(root, curu)) xor (

s(root, targetv) xor s(root, curu))

由於對稱差的交換律、結合律:

t(curv, curu) xor 

t(targetv, curu)

= s(root, curv) xor

s(root, targetv)

兩邊同時xor 

t(curv, curu):

t(targetv, curu)

= t(curv, curu) xor 

s(root, curv) xor 

s(root, targetv)

發現最後兩項很爽……哇哈哈

t(targetv, curu)

= t(curv, curu) xor t

(curv, 

targetv)

(有公式恐懼症的不要走啊 t_t)

也就是說,更新的時候,xor 

t(curv, 

targetv)就行了。

即,對curv到targetv路徑(除開lca(curv, targetv))上的結點,將它們的存在性取反即可。 」

——vfk部落格

這樣就很好處理了,只要把lca扔出去,考慮剩下的部分,轉移一下就可以了。查答案的時候再把lca那個點反過來,就能統計出答案了。

這樣,模擬序列上的莫隊,我們對樹上的詢問也可以分塊了,時間複雜度同樣是o(nsqrt(n))。

bzoj 3757貌似是同乙個題,就貼這個**吧……cot2沒寫……spoj上怕t……

#include#include#include#include#include#include#include#includeusing namespace std;

inline int getint()

const int maxn=100010;

int n,m,k,lca,u,v,c[maxn],dfn[maxn],belongn[maxn];

int tot,root,dfs_clock,remain;

int head[maxn],to[maxn],next[maxn],cnt;

int anc[maxn][21],dep[maxn],log[maxn];

int stack[maxn],top;

int p[maxn],ans,con[maxn];

bool used[maxn];

struct query

} stack[++top]=x;

return size+1;

}int lca(int p,int q)

void work(int u,int v,int lca)

else

u=anc[u][0];

} while(v!=lca)

else

v=anc[v][0]; }}

int main()

for(int i=1;i<=m;i++)

remain=dfs(root);

for(int i=1;i<=remain;i++) belongn[stack[top--]]=tot;

sort(q+1,q+m+1);

log[0]=-1;

for(int i=1;i<=n;i++) log[i]=log[i>>1]+1;

for(int i=1;i<=log[n];i++)

for(int j=1;j<=n;j++)

anc[j][i]=anc[anc[j][i-1]][i-1];

work(q[1].u,q[1].v,lca=lca(q[1].u,q[1].v));

if(!used[lca])

else

con[q[1].sub]=ans;

if(p[q[1].a]!=0&&p[q[1].b]!=0) con[q[1].sub]--;

if(!used[lca])

else

for(int i=2;i<=m;i++)

else

con[q[i].sub]=ans;

if(p[q[i].a]!=0&&p[q[i].b]!=0&&q[i].a!=q[i].b) con[q[i].sub]--;

if(!used[lca])

else

} for(int i=1;i<=m;i++) printf("%d\n",con[i]);

return 0;

}

感覺好神啊……vfk的那個方法確實好用,這個演算法也好有趣。

(未完待續……)

wc2013 糖果公園 待填坑

#include#include#include#include#include#include#include#includeusing namespace std;

typedef long long ll;

const int maxn=300010;

int n,m,u,v,q,k,type,c[maxn],ctmp[maxn];

ll v[maxn],w[maxn],ans,con[maxn];

int head[maxn],to[maxn],next[maxn],cnt;

int belongn[maxn],tot,remain;

int anc[maxn][20],dep[maxn],log[maxn];

int stack[maxn],top;

int qtot,ctot,query_clock=1;

int p[maxn],lca;

bool used[maxn];

struct qq

} stack[++top]=x;

return size+1;

}int lca(int p,int q)

void reverse(int x)

void changever(int x,int d)

else }

void work(int x,int y,int lca)

int main()

for(int i=1;i<=qtot;i++) printf("%lld\n",con[i]);

return 0;

}

樹上莫隊演算法

樹上莫隊,顧名思義就是把莫隊搬到樹上。我們從一道題目入手 sdoi2018 原題識別 spoj count on a tree ii 題目意思很明確 給定乙個 n 個節點的樹,每個節點表示乙個整數,問 u 到 v 的路徑上有多少個不同的整數。像這種不帶修改數顏色的題首先想到的肯定是樹套樹莫隊,那麼如...

樹上莫隊演算法

樹上莫隊,顧名思義就是把莫隊搬到樹上。我們從一道題目入手 sdoi2018 原題識別 spoj count on a tree ii 題目意思很明確 給定乙個 n 個節點的樹,每個節點表示乙個整數,問 u 到 v 的路徑上有多少個不同的整數。像這種不帶修改數顏色的題首先想到的肯定是樹套樹莫隊,那麼如...

莫隊演算法學習筆記(三) 樹上莫隊

樹上莫隊的核心思想,就是將一棵樹轉化成乙個序列,然後用普通莫隊來搞。以一棵樹為例 要想對這棵樹進行樹上莫隊,我們第一步就是用乙個 s 陣列把它的括號序存下來 id 12 3456 78910 1112 1314 1516 s 12 4788 7455 2366 31 同時,我們用 i 陣列儲存每個數...