1. 程式人生 > 實用技巧 >Kruscal 重構樹 ------貨車運輸

Kruscal 重構樹 ------貨車運輸

P1967 貨車運輸

Kruscal重構樹的板子題,加上求LCA

需要的知識:並查集,Kruscal最小(大)生成樹,倍增求LCA,(DFS)

您說您不知道什麼是Kruscal重構樹

Kruscal重構樹就是在跑Kruscal最小(大)生成樹的時候把找到的那條符合條件(聯通不同兩個聯通塊的邊)的邊,然後把這條邊的起點和終點都連上一個新的節點,這個新的節點的點權就是原來這條邊的邊權。

Kruscal重構樹有什麼用?

Kruscal重構樹有個神奇的、用得很廣的性質,就是對於這顆樹上的葉子節點(其實也就是原圖中的節點)跑LCA的話,它們的LCA的點權就是從其中一個葉子節點出發到另外一個葉子節點所過路徑中的最大(小)邊。

如果你是跑最小生成樹的話,你得到的LCA是路徑中最大邊最小,否則跑最大生成樹就是最小邊最大。

這道題要求從某個點到另外一個點的路徑中最小一條邊最大,先跑一遍Kruscal最大生成樹建一棵Kruscal重構樹即可。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 100005
int n,m,R=0,t=0;
int fa[MAXN],start[MAXN],data[MAXN],vis[MAXN];
int deep[MAXN],parents[MAXN][25],Q;
struct road{int u,v,w;}r[MAXN],z[MAXN];

int cmp(road A,road B){return A.w > B.w;} int CMP(road A,road B){return A.u < B.u;} int prepare(); int DFS(int x,int from); int LCA(int x,int y); int find(int x){ int k=x,p; while(x != fa[x])x=fa[x]; while(k != fa[k])p=k,k=fa[k],fa[p]=x; return x;//查詢以及路徑壓縮 } int main(){ prepare();//預處理 DFS(R,
0);//預處理 cin >> Q; for (int i = 1 ; i <= Q ; i ++){ int X,Y,W; cin >> X >> Y ;//讀入 if( find(X) != find(Y) ){cout << "-1" << endl; continue;} cout << LCA(X,Y) << endl; } return 0; } int LCA(int x,int y){ if(deep[x] > deep[y])swap(x,y); int MAXI = 0;//倍增求LCA的板子 while(deep[parents[y][MAXI]] > deep[x])MAXI++;//找最大的i可以是多大 for (int i = MAXI ; i >= 0 ; i --) if(deep[parents[y][i]] >= deep[x])y = parents[y][i];//y和x提到同一高度 if(x == y)return data[x];//如果x本來就是xy的最近公共祖先,直接返回 MAXI = 0; while(deep[parents[x][MAXI]] > 0)MAXI ++;//確定最大的i for (int i = MAXI ; i >= 0 ; i --) if( parents[x][i] != parents[y][i]) x = parents[x][i],y = parents[y][i];//一同往上跳 return data[parents[x][0]]; } int DFS(int x,int from){ deep[x] = deep[from]+1;vis[x]=1; parents[x][0]=from;//DFS以及預處理Deep和父親節點的板子 for (int i = 1 ; i <= 21 ; i ++) parents[x][i]=parents[parents[x][i-1]][i-1]; for (int i = start[x] ; i <= t && z[i].u == x; i ++) if(vis[z[i].v] != 1)DFS(z[i].v,x); return 0; } int prepare(){ cin >> n >> m ; R = n ; for (int i = 1 ; i <= m ; i ++) cin >> r[i].u >> r[i].v >> r[i].w; sort( r+1 , r+1+m , cmp );//讀入以及對邊進行排序 for (int i = 1 ; i <= n ; i ++)fa[i] = i ;//初始化並查集陣列 for (int i = 1 ; i <= m ; i ++){ int p=find(r[i].u),to=find(r[i].v);//找當前邊的兩個端點是否屬於同一個聯通快 if( p != to){ R++;fa[R] = R,fa[p] = fa[to] = R;//重新歸為同一個並查集 data[R] = r[i].w ;//賦點值為邊權 t++;z[t].u = R ; z[t].v = p ;//重新連邊,開始重構 t++;z[t].u = R ; z[t].v = to ; } } sort(z+1,z+1+t,CMP);//按起點排序,便於我跑假的前向星 int ls=-1; for (int i = 1 ; i <= m ; i ++) if(z[i].u != ls)ls = z[i].u,start[ls] = i;//假的前向星 return 0; }