Kruskal重构树

最小生成树

将一个图的带权边删掉若干条,使得图最终形成一棵树,并且满足边权之和最小。

kruskal

若我们要求最小生成树,我们贪心的从边权最小的边开始讨论,借用并查集,判断该边能否加入树中。若可以加入树中,则加入。

Kruskal重构树 

过程:

kruskal算法执行的过程中,如果加入了第$i$条边,我们得到一个新节点$x$,$x$的点权为$i$号边的边权,将$x$与两个端点的$father$相连。最终,我们将得到一颗 拥有$2n-1$个节点的树,即Kruskal重构树。

性质: 

1. 原本图中的$n$个节点,均为树上的叶子节点;

2. 重构树是一个大根堆(按最小生成树重构);

3. 重构树(按最小生成树重构)中,任意两个节点$a,b$的,在原图上的路径中的最大边权的最小值为$LCA(a,b)$的点权;

4. 若原图不连通,会得到重构树森林;

5. 重构树的节点总数为$2n-1$,它是一个二叉树。

 

例题: 

例题一:[NOIP2013 提高组] 货车运输

简要题意:

给出一个图,求$x$到$y$的路径中最小边权的最大值。

分析:

我们要求最小边权的最大值,可以按最大生成树的原则建立Kruskal重构树。

询问一对点$(x, y)$,$LCA(x, y)$的点权即答案。

#include<bits/stdc++.h>
using namespace std;
#define re register int

const int N=5e5+5;

vector<int>G[N]; // 重构树 
int fa[N][30], dep[N];
void dfs(int x) // 重构树lca初始化 
{
    dep[x] ++;
    for(re i=1;i<=20;++i) fa[x][i] = fa[fa[x][i-1]][i-1];
    for(re v : G[x])
    {
        fa[v][0] = x;
        dep[v] = dep[x];
        dfs(v);
    }
}
int lca(int x, int y)
{
    if(dep[x] < dep[y]) swap(x, y);
    for(re i=0, d=dep[x]-dep[y];i<=20;++i)
    if(d & (1<<i)) x = fa[x][i];
    if(x == y) return x;
    for(re i=20;i>=0;--i)
    if(fa[x][i] != fa[y][i])
    x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}

struct node{int x, y, v;}A[N];
bool cmp(node x, node y){return x.v > y.v;}
int total, n, m, f[N], val[N];
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
void kruskal()
{
    for(re i=1, nn=(n<<1);i<=nn;++i)f[i]=i; // 初始化并查集 
    sort(A+1, A+1+m, cmp);  
    
    int k = 0;
    total = n;
    while(k < m)
    { // Kruskal 
        int v = A[++k].v;
        int f1 = getf(A[k].x);
        int f2 = getf(A[k].y);
        
        if(f1 != f2)
        {
            val[++total] = v; // 新节点的点权即为该边的边权 
            G[total].push_back(f1); // 将新节点连向两端点的father 
            G[total].push_back(f2);
            f[f1] = total;
            f[f2] = total;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(re i=1;i<=m;++i)
    {
        int x, y, z;
        scanf("%d%d%d",&x,&y,&z);
        A[i] = (node){x, y, z};
    }
    kruskal();
    dfs(total);
    scanf("%d",&m);
    while(m--)
    {
        int x, y;
        scanf("%d%d",&x,&y);
        if(getf(x) != getf(y)) puts("-1"); // 不连通 
        else printf("%d\n", val[lca(x, y)]);
    }
    return 0;
}
View Code

例题二:删边(nkoj P8437)

简要题意:

已知一个图,每次询问图中$ki$个不同的点,我们求最小的$Ans$,使得将图中所有边权小于等于$Ans$的边删除后,这$ki$个点互不连通。

分析:

删除小于等于$Ans$的边,且我们要求$Ans$的最小值,可以按最大生成树的原则建立Kruskal重构树。

构完树后,任意两点$x,y$的$LCA$的点权,即在原图中两点路径上最小边权的最大值。也就是说,我们删除小于等于$val[LCA(x, y)]$的边后,$(x, y)$肯定不连通。

这里有两种想法:

1. 求$ki$个点的公共LCA,但画个图我们可以知道,这样不一定正确;

2. $ki$个点两两的$LCA$的点权的最大值。

很明显,第二种想法才是正确的。

但是如果我们真的两两都要求一遍$LCA$,然后求各个$LCA$的点权的最大值,这耗时$O(k2)$,很不优秀。

结合图我们可以知道,dfs序相近的两点,可以得到深度较大的$LCA$,深度越大,该点的点权越大,更有可能是$Ans$的值。

所以我们的Ans=max(val[LCA(x1,x2)], val[LCA(x2, x3)], ......)。

#include<stdio.h>
#include<vector>
#include<algorithm>

using namespace std;
#define re register int

static char buf[1000000],*p1=buf,*p2=buf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
inline int read(){
    int x=0;
    char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=x * 10 + c - 48,c=getchar();
    return x;
}

const int N=2e6+5;

int n, m;
vector<int>G[N];
int f[N], val[N], total;
struct node{int x, y, v;}A[N];
bool cmp(node x, node y){return x.v>y.v;}
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
void kruskal()
{
    for(re i=1, nn=(n<<1);i<=nn;++i)f[i]=i;
    int k=0;
    total=n;
    sort(A+1, A+1+m, cmp);
    while(k<m)
    {
        k++;
        int f1=getf(A[k].x), f2=getf(A[k].y), v=A[k].v;
        if(f1 != f2)
        {
            val[++total]=v;
            f[f1]=total;
            f[f2]=total;
            G[total].push_back(f1);
            G[total].push_back(f2);
        }
    }
}

int a[N], in[N], dep[N], fa[N][30], dfn;
void dfs(int x)
{
    in[x]=++dfn;
    dep[x]++;
    for(re i=1;i<=20;++i)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(re v:G[x])
    {
        dep[v]=dep[x];
        fa[v][0]=x;
        dfs(v);
    }
}
int lca(int x, int y)
{
    if(dep[x]<dep[y])swap(x, y);
    for(re i=0,d=dep[x]-dep[y];i<=20;++i)if(d&(1<<i))x=fa[x][i];
    if(x==y)return x;
    for(re i=20;i>=0;--i)
    if(fa[x][i]!=fa[y][i])
    x=fa[x][i], y=fa[y][i];
    return fa[x][0];
}
bool cmi(int x, int y){return in[x]<in[y];}
int main()
{
    int T;
    n=read();
    m=read();
    T=read();
    for(re i=1;i<=m;++i)
    {
        int x=read(), y=read(), z=read();
        A[i]=(node){x, y, z};
    }
    
    kruskal();
    dfs(total);
    int Ans=0, lastans=0;
    while(T--)
    {
        Ans=0;
        m=read();
        for(re i=1;i<=m;++i)
            a[i]=lastans^read();
        
        sort(a+1, a+1+m, cmi); // 按dfs序排序 
        for(re i=1;i<m;++i) Ans = max(Ans, val[lca(a[i], a[i+1])]);
        
        lastans = Ans;
        printf("%d\n", Ans);
    }
    return 0;
}
View Code

 

posted @ 2021-08-16 21:41  kzsn  阅读(124)  评论(0)    收藏  举报