至於爲什麼要用最大生成樹!?
理由:因爲問最大載重,如果要加大載重的話,對於選擇的路肯定權重越大越好,所以貪心地想,得用到最大生成樹。然後兩點之間的最大載重用LCA去尋找就可以了。
最大生成樹+LCA:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
const int inf = 0x3f3f3f3f;
int n,m;
struct Edge{
int u,v,nxt,w;
}edge[maxn];
int head[maxn],tot,pre[maxn],lg[maxn],depth[maxn],fa[maxn][30],w[maxn][30];
bool vis[maxn];
inline void init(){ // Initiallize
for(int i = 0; i < maxn; ++i) pre[i] = i,head[i] = -1;
for(int i = 1; i < maxn; ++i) lg[i] = lg[i-1] + (1<<lg[i-1]==i);
tot = 0;
}
struct Edge2{
int u,v,w;
bool operator <(const Edge2&h)const{
return w > h.w;
}
}e[maxn];
inline void addedge(int u,int v,int w){
edge[++tot] = {u,v,head[u],w};
head[u] = tot;
}
int FindPre(int x){
if(pre[x]==x) return x;
return pre[x] = FindPre(pre[x]);
}
inline void Krusual(){ // Build MaxSTree
sort(e,e+m);
for(int i = 0; i < m; ++i){
Edge2 &ee = e[i];
int fu = FindPre(ee.u),fv = FindPre(ee.v);
if(fu!=fv){
pre[fv] = fu;
addedge(ee.u,ee.v,ee.w);
addedge(ee.v,ee.u,ee.w);
}
}
}
void dfs(int u,int ff){
vis[u] = 1;
fa[u][0] = ff;
depth[u] = depth[ff]+1;
for(int i = 1; (1<<i) <= depth[u]; ++i){
fa[u][i] = fa[fa[u][i-1]][i-1];
w[u][i] = min(w[u][i-1],w[fa[u][i-1]][i-1]);
}
for(int i = head[u]; ~i; i = edge[i].nxt){
Edge &e = edge[i];
if(vis[e.v]) continue;
w[e.v][0] = e.w;
dfs(e.v,u);
}
}
int LCA(int x,int y){
if(FindPre(x)!=FindPre(y)) return -1;
int ans = inf;
if(depth[x] < depth[y]) swap(x,y);
while(depth[x] > depth[y]){
ans = min(ans,w[x][lg[depth[x]-depth[y]]-1]);
x = fa[x][lg[depth[x]-depth[y]]-1];
}
if(x==y) return ans;
for(int i = lg[depth[x]]-1; i >= 0; --i){
if(fa[x][i]!=fa[y][i]){
ans = min(ans,min(w[x][i],w[y][i]));
x = fa[x][i];
y = fa[y][i];
}
}
return ans = min(ans,min(w[x][0],w[y][0]));
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
init();
cin >> n >> m;
for(int i = 0; i < m; ++i){
int x,y,z; cin >> x >> y >> z;
e[i] = Edge2{x,y,z};
}
Krusual(); // Build the Graph MST;
for(int i = 1; i <= n; ++i){
if(!vis[i]){
depth[i] = 0;
fa[i][0] = i;
w[i][0] = inf;
dfs(i,i);
}
}
int q; cin >> q;
for(int i = 0; i < q; ++i){
int x,y; cin >> x >> y;
cout << LCA(x,y) << endl;
}
return 0;
}
Kruskal重構樹
其實從題意來講,這題就是兩點間簡單路徑的最小邊權值(用Kruskal重構樹解決就可以啦)
kruskal重構樹基於kruskal算法,能巧妙地求解詢問從A點走到B點的所有路徑中,最長的邊最小值是多少,或者最短的邊的最大值之類的問題。
由於之間沒有接觸過重構樹,所以在這裏把重構樹的具體原理/算法/性質都丟在這裏,方便自己後期整理複習。
前置知識:
Kruskal 最小生成樹算法
DSU 並查集
LCA 樹上倍增
建樹過程(類似於Kruskal算法,不然爲什麼叫Kruskal重構樹呢…
1、 將題目中的邊讀入(Struct Edge)
2、 將邊權排序 (排序方式決定重構樹性質)
3、依次遍歷每條邊
4、若改變連接的兩個節點u和v 不在一個並查集內就新建一個結點,該點點權爲這條邊的邊權
5.找到u,v所在並查集的根, ,連邊 ,並更新並查集
其實也就是最小生成樹的過程中,原本是直接連,而現在是新創建一個結點,然後相連。
這棵樹是以最後新建的結點爲根的有根樹,若原圖不連通,即建出的是一個森林,那麼就遍歷每個節點,找到其並查集的根作爲其所在樹的根
以下是性質:
1.kruskal重構樹是一顆二叉樹,並符合二叉堆的性質。
2.原樹兩點間的的最大邊權就是kruskal重構樹上兩點的lca的權值。
3.重構樹中代表原樹中的點的節點全是葉子節點,其餘節點都代表了一條邊的邊權。