1. 程式人生 > >[您有新的未分配科技點][BZOJ3545&BZOJ3551]克魯斯卡爾重構樹

[您有新的未分配科技點][BZOJ3545&BZOJ3551]克魯斯卡爾重構樹

return 講解 date 要求 strong 重構 mission amp null

這次我們來搞一個很新奇的知識點:克魯斯卡爾重構樹。它也是一種圖,是克魯斯卡爾算法求最小生成樹的升級版首先看下面一個問題:BZOJ3545 Peaks。

在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之間有雙向道路相連,共M條路徑,每條路徑有一個困難值,這個值越大表示越難走。

現在有Q組詢問,每組詢問詢問從點v開始只經過困難值小於等於x的路徑所能到達的山峰中第k高的山峰,如果無解輸出-1。N<=1e5,M,Q<=5*1e5

上面這個題沒有要求在線,因此我們可以離線構造最小生成樹,然後當小於等於一個詢問的困難值的所有邊都加入後,就可以查詢當前的詢問點。

這種操作只需要主席樹上樹+啟發式合並就可以解決了。(沒有了解過啟發式合並的同學可以移步我的題解:)但是如果強制在線呢?

BZOJ3551 Peaks加強版,在上一題基礎上強制在線。

可以用來解決一系列“查詢從某個點出發經過邊權不超過val的邊所能到達的節點”的問題,可以和其他數據結構(比如主席樹)套用來維護更加復雜的詢問。

克魯斯卡爾重構樹的核心思想是,當添加最小生成樹中的邊的時候,不在兩個點間直接加邊,而是新建節點,讓邊的兩個端點所在的聯通塊的代表點分別作為它的左右兒子節點,然後這個新建的點,就成為這整個連通塊的代表點,點權為連邊的值(最開始n個葉子節點點權為0)。比如看下面這張圖:首先連接(1,2),新建一個點5。再連接(3,4),新建一個點6。然後連接(1,3),連接它們各自聯通塊的代表點(5,6),再新建一個點7。

技術分享

這樣得到的樹有一個很優雅的性質:一個點的所有子樹節點的權值都小於等於它的權值,並且從它開始逐漸向子節點移動,權值是單調不上升的。這個性質是顯然的,因為我們在構造樹的時候是從小到大插入的邊,因此父親節點的權值一定大於等於子節點的值。

查詢時,首先可以在樹上二分得到當前查詢點所能夠到達的最遠的祖先點,那麽它能夠到達的連通塊就是祖先點的子樹中所有的葉節點。

然後我們按dfs序維護一個主席樹上樹就可以解決了。沒有了解過主席樹上樹的同學可以移步我之前的講解

代碼見下:

 1 #include <cstring>
 2 #include <cstdio>
 3 #include <algorithm>
 4
#include <ctime> 5 using namespace std; 6 const int N=100100; 7 int h[N*2],val[N*2],n,tot,num,cnt,stack[N],e,adj[N*2]; 8 int f[N*2][20],bin[25],fa[N*2],l[N*2],r[N*2]; 9 struct edge{int qi,zhong,val;}intn[N*5]; 10 struct link{int zhong,next;}s[N*2]; 11 inline void mission1(int rt){for(int i=1;bin[i]<=n;i++)f[rt][i]=f[f[rt][i-1]][i-1];} 12 inline void add(int qi,int zhong){s[++e].zhong=zhong;s[e].next=adj[qi];adj[qi]=e;} 13 inline bool mt(const edge &a,const edge &b){return a.val<b.val;} 14 int find(int a){return (fa[a]==a)?a:fa[a]=find(fa[a]);} 15 struct node 16 { 17 int cnt;node *ch[2]; 18 node(){cnt=0;ch[0]=ch[1]=NULL;} 19 inline void update(){cnt=ch[0]->cnt+ch[1]->cnt;} 20 }*null=new node(),*root[2*N]; 21 inline node* newnode(){node *o=new node();o->ch[0]=o->ch[1]=null;return o;} 22 void insert(node *&o,node *old,int l,int r,int pos) 23 { 24 o->cnt=old->cnt+1; 25 if(l==r)return; 26 int mi=(l+r)>>1; 27 if(pos<=mi)o->ch[1]=old->ch[1],o->ch[0]=newnode(),insert(o->ch[0],old->ch[0],l,mi,pos); 28 else o->ch[0]=old->ch[0],o->ch[1]=newnode(),insert(o->ch[1],old->ch[1],mi+1,r,pos); 29 o->update(); 30 } 31 inline int query(int a,int x,int k) 32 { 33 int le=1,ri=tot; 34 for(int j=18;~j;j--) 35 if(f[a][j]&&val[f[a][j]]<=x)a=f[a][j]; 36 node *a1=root[r[a]],*a2=root[l[a]-1]; 37 if(a1->cnt-a2->cnt<k)return -1; 38 while(le<ri) 39 { 40 int tmp=a1->ch[1]->cnt-a2->ch[1]->cnt,mi=(le+ri)>>1; 41 if(tmp>=k)a1=a1->ch[1],a2=a2->ch[1],le=mi+1; 42 else a1=a1->ch[0],a2=a2->ch[0],k-=tmp,ri=mi; 43 } 44 return stack[ri]; 45 } 46 void dfs(int rt) 47 { 48 mission1(rt);l[rt]=++num; 49 if(rt<=n)insert(root[num],root[num-1],1,tot,h[rt]); 50 else root[num]=root[num-1]; 51 for(int i=adj[rt];i;i=s[i].next)dfs(s[i].zhong); 52 r[rt]=num; 53 } 54 int main() 55 { 56 int m,q,ans=0,v,x,k;scanf("%d%d%d",&n,&m,&q); 57 bin[0]=1;for(int i=1;i<=22;i++)bin[i]=bin[i-1]<<1; 58 null->ch[0]=null->ch[1]=null; 59 for(int i=1;i<=n*2;i++)fa[i]=i; 60 for(int i=1;i<=n;i++)scanf("%d",&h[i]),stack[i]=h[i]; 61 for(int i=1;i<=m;i++)scanf("%d%d%d",&intn[i].qi,&intn[i].zhong,&intn[i].val); 62 sort(stack+1,stack+n+1); 63 tot=unique(stack+1,stack+n+1)-stack-1; 64 for(int i=1;i<=n;i++)h[i]=lower_bound(stack+1,stack+tot+1,h[i])-stack; 65 sort(intn+1,intn+m+1,mt);cnt=n; 66 for(int i=1;i<=m;i++) 67 { 68 int u=find(intn[i].qi),v=find(intn[i].zhong); 69 if(u!=v) 70 { 71 val[++cnt]=intn[i].val,fa[u]=fa[v]=cnt; 72 add(cnt,u),add(cnt,v),f[u][0]=f[v][0]=cnt; 73 if(cnt-n==n-1)break; 74 } 75 } 76 for(int i=0;i<=cnt;i++)root[i]=newnode(); 77 for(int i=1;i<=cnt;i++)if(!l[i])dfs(find(i)); 78 while(q--) 79 { 80 scanf("%d%d%d",&v,&x,&k); 81 if(ans!=-1)v^=ans,x^=ans,k^=ans;/*去掉這句強制在線可以ACbzoj3545*/ 82 printf("%d\n",ans=query(v,x,k)); 83 } 84 }

克魯斯卡爾重構樹是個比較小眾的知識點,但在處理對口的操作時十分強大。下次你再看到類似詢問的時候,不妨想一想克魯斯卡爾重構樹,也許就會柳暗花明又一村

[您有新的未分配科技點][BZOJ3545&BZOJ3551]克魯斯卡爾重構樹