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

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

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

kruskal求最小生成樹的過程,如果把加入的一個邊新建一個節點的話,並且把k1,k2的father設為新點的話,會得到一個2*n大小的樹

實際上已經非常明白地表示kruskal這個過程了。這個樹叫kruskal重構樹

每個點的權值定義為所代表的邊的權值。葉子節點權值最優。

由於貪心,所以樹上所有點,從兒子到祖先權值單調不優。(這是最關鍵的性質)

樹的結構我們非常熟悉

所以各種演算法紛至沓來。

 

具體怎麼用?

BZOJ3545 Peaks:

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

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

(不強制線上的話,直接離線排序+線段樹合併或者啟發式合併。(類似“永無鄉”))

強制線上呢?

 

kruskal重構樹登場!

對於只走小於x的邊,會得到一個可以到達的聯通塊。

最小生成樹的邊上走,保證這個聯通塊是極大的。

聯通塊高度的第k大就是答案。

發現這個聯通塊對應重構樹的一個子樹!

重構樹上,從v開始往上倍增找到最後一個權值小於等於x的點p

p對應的子樹的葉子就是所有的聯通塊的點。(有點類似SAM的fail樹?)

所以區間第k大,用dfs序處理一下,+主席樹維護即可。

 

更近一些的:[NOI2018]歸程 

如果想到kruskal重構樹(感覺學了應該都能想到,畢竟適用面也不是很廣),那麼這題就水了。

所能開車到達的集合裡選擇距離家最近的點下車。

kruskal重構樹建出來(最大生成樹),葉子賦予一個dis到1的距離。

dis用dij跑(因為出題人卡spfa)

然後dfs重構樹處理資訊,然後倍增處理詢問。大功告成!

 

程式碼:

(很醜陋,見縫插針,而且mi、dep陣列其實根本不需要)

#include<bits/stdc++.h>
#define
reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=200000+5; const int M=400000+5; const ll inf=0x3f3f3f3f3f3f3f3f; ll dis[N]; ll ans[2*N]; int val[2*N]; int n,m; struct node{ int nxt,to; int val; }e[4*N]; int hd[2*N],cnt; void add(int x,int y,int z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt; } int tot; namespace dij{ bool vis[N]; struct po{ int x,d; po(int xx,int dd){ x=xx;d=dd; } bool friend operator <(po a,po b){ return a.d>b.d; } }; priority_queue<po>q; void dij(){ memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); while(!q.empty()) q.pop(); dis[1]=0; q.push(po(1,0)); while(!q.empty()){ po now=q.top();q.pop(); if(vis[now.x]) continue; vis[now.x]=1; dis[now.x]=now.d; for(reg i=hd[now.x];i;i=e[i].nxt){ int y=e[i].to; if(vis[y]) continue; q.push(po(y,dis[now.x]+e[i].val)); } } for(reg i=1;i<=n;++i){ ans[i]=dis[i]; //mi[i][0]=0x3f3f3f3f; } } } struct edge{ int x,y; int l,a; bool friend operator <(edge a,edge b){ return a.a>b.a; } }b[M]; //represent the value of edge namespace kruscal{ int fa[2*N]; int fin(int x){ return fa[x]==x?x:fa[x]=fin(fa[x]); } void main(){ for(reg i=1;i<=n;++i) { fa[i]=i;val[i]=0x3f3f3f3f; } tot=n; // cout<<" tot "<<tot<<endl; sort(b+1,b+m+1); for(reg i=1;i<=m;++i){ int k1=fin(b[i].x),k2=fin(b[i].y); // cout<<" bb "<<tot<<" "<<i<<" : "<<b[i].x<<" "<<b[i].y<<" rt "<<k1<<" sand "<<k2<<endl; if(k1!=k2){ ++tot; fa[tot]=tot; ans[tot]=inf; add(tot,k1,0); add(tot,k2,0); fa[k1]=fa[k2]=tot; val[tot]=b[i].a; } } } } int fa[2*N][20]; int mi[2*N][20]; int dep[2*N]; void dfs(int x,int d){ dep[x]=d; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; fa[y][0]=x; mi[y][0]=min(val[y],val[x]); dfs(y,d+1); ans[x]=min(ans[x],ans[y]); } } ll query(int x,int p){ for(reg j=19;j>=0;--j){ if(fa[x][j]){ if(mi[x][j]>p) x=fa[x][j]; } } return ans[x]; } void clear(){ memset(fa,0,sizeof fa); memset(mi,0x3f,sizeof mi); memset(dep,0,sizeof dep); memset(hd,0,sizeof hd); cnt=0; } int main(){ int T; rd(T); while(T--){ rd(n);rd(m); clear(); for(reg i=1;i<=m;++i){ rd(b[i].x);rd(b[i].y);rd(b[i].l);rd(b[i].a); add(b[i].x,b[i].y,b[i].l); add(b[i].y,b[i].x,b[i].l); } dij::dij(); // cout<<" after dij "<<endl; memset(hd,0,sizeof hd); cnt=0; kruscal::main(); // cout<<" after kruc "<<endl; dfs(tot,1); // cout<<" after dfs "<<endl; for(reg j=1;j<=19;++j){ for(reg i=1;i<=tot;++i){ fa[i][j]=fa[fa[i][j-1]][j-1]; mi[i][j]=min(mi[i][j-1],mi[fa[i][j-1]][j-1]); } } // cout<<" tot "<<tot<<endl; // for(reg i=1;i<=tot;++i){ // cout<<" ii "<<i<<" val "<<val[i]<<" ans "<<ans[i]<<" fafa "<<fa[i][0]<<endl; // } int q,k,s; rd(q);rd(k);rd(s); ll las=0; int v,p; while(q--){ rd(v);rd(p); v=(v+k*las-1)%n+1; p=(p+k*las)%(s+1); printf("%lld\n",las=query(v,p)); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/8 18:49:37 */

 

這個演算法適用面:
線上處理詢問在經過小於(大於)某個邊權的邊所能到的集合裡的資訊。

主要性質是祖先權值的單調不優。

 

這個演算法是對kruskal求最小生成樹的操作過程的樹形結構化,從而精確剖析過程,使得維護方便。

有異曲同工之妙的是動態點分治的分治樹,也是對操作過程的樹形結構化。