P1967 貨車運輸( 最大生成樹+LCA or Kruskal重構樹)

至於爲什麼要用最大生成樹!?
理由:因爲問最大載重,如果要加大載重的話,對於選擇的路肯定權重越大越好,所以貪心地想,得用到最大生成樹。然後兩點之間的最大載重用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 不在一個並查集內就新建一個結點nodenode,該點點權爲這條邊的邊權
5.找到u,v所在並查集的根uiu_i,viv_i ,連邊(node,ui)(node,vi)(node,u_i)(node,v_i) ,並更新並查集pre[ui]=Nodenew,pre[vi]=Nodenewpre[u_i]=Node_{new},pre[v_i]=Node_{new}
其實也就是最小生成樹的過程中,原本是直接連u,vu,v,而現在是新創建一個結點,然後相連。

這棵樹是以最後新建的結點爲根的有根樹,若原圖不連通,即建出的是一個森林,那麼就遍歷每個節點,找到其並查集的根作爲其所在樹的根

以下是性質:

1.kruskal重構樹是一顆二叉樹,並符合二叉堆的性質。
2.原樹兩點間的的最大邊權就是kruskal重構樹上兩點的lca的權值。
3.重構樹中代表原樹中的點的節點全是葉子節點,其餘節點都代表了一條邊的邊權。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章