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;
}

 

posted @ 2020-08-27 13:09  MYCui  阅读(144)  评论(1编辑  收藏  举报