Stephen Horizon
Stephen Horizon

『OI笔记』次小生成树

前置芝士

  • 一点点图论芝士
  • Kruskal(或者你用Prim也行),用来求最小生成树

定义

  定义就是字面意思,无需解释。

思路

  先将输入的图的最小生成树求出来,再枚举没有增加过的边,将他们加入到最小生成树当中去。新增一条边之后,我们就可以得到一个基环树
  接下来,我们在这棵基环树上找到环,并删除环上最长的边,这是我们就可以得到一颗新的树。通过枚举每一颗新的树,找到这些树中的最小的树,这就是最终的次小生成树了。

具体步骤

Step 1

  首先当然是要跑一遍kruskal拉! :idea:

Step 2

  我们要解决一个问题:那就是如何快速地找到环上最大的边?当然是预处理了。所以,我们可以在Kruskal(Prim党自便)求最小生成树的时候顺便存一下两点之间的最长路径。
  怎么存?由于Kruskal和Prim的算法原理有些不同,所以Prim的同学可以先退场了。由于我们只是要求两个点所在的环上的最长边,所以就等于在这两个点合并前时候他们所在的子集的最长边。
  所以,我们就要储存好该集合(树)下所有的点。这个不难,用vector就可以了。还有,因为kruskal在开始加边前经过sort,所以在后面出现的边的边权一定大于前面出现的,所以直接等于就可以了。转存的代码如下:

//更新最大值
//gra是用来存集合(树)中的点的
int l1=gra[x].size();
int l2=gra[y].size();
for (int p=0;p<l1;p++)
    for (int q=0;q<l2;q++)
        mx[gra[x][p]][gra[y][q]]=mx[gra[y][q]][gra[x][p]]=edge[i].s;
//合并时需要将被合并的点所在的集合的所有点进行合并(有绕口令内味了bushi)
fa[x]=y;
for (int j=0;j<l1;j++)
    gra[y].push_back(gra[x][j]);

注意,以上代码是在kruskal里边的!!

Step 3

  最后统计答案的时候,新建一个ans,原来最小生成树的权值和就用sum把。我们按照思路上的,枚举每一个不再最小生成树中的边,然后对ans进行更新就OK了!

void output()
{
    int ans=INT_MAX;
    for (int i=1;i<=m;i++)
    {
        if (edge[i].vis) continue;
        ans=min(ans,sum+edge[i].s-mx[edge[i].u][edge[i].v]);
    }
    if (ans==sum) 
        cout<<"Not Unique!"<<endl;
    else cout<<sum<<endl;
}

例题及代码

POJ 1679 The Unique MST
POJ不给用万能头文件大大的差评!XPINXPINXPINXPINXPINXPINXPIN

#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;
int n,m;
struct data
{
    int u,v,w;
    bool vis;
} p[20010];
vector<int>G[110];
int per[110],maxd[110][110];
bool cmp(data a,data b)
{
    return a.w < b.w;
}
int Union_Find(int x)
{
    return x == per[x] ? x: per[x] = Union_Find(per[x]);
}
void kruskal()
{
    sort(p,p+m,cmp);
    for(int i=0; i<=n; i++)
    {
        G[i].clear();
        G[i].push_back(i);
        per[i]=i;
    }
    int sum=0,k=0;
    for(int i=0; i<m; i++)
    {
        if(k==n-1)  break;
        int x1=Union_Find(p[i].u),x2=Union_Find(p[i].v);
        if(x1!=x2)
        {
            k++;
            p[i].vis=1;
            sum+=p[i].w;
            int len_x1=G[x1].size();
            int len_x2=G[x2].size();
            for(int j=0; j<len_x1; j++)
                for(int k=0; k<len_x2; k++)
                    maxd[G[x1][j]][G[x2][k]]=maxd[G[x2][k]][G[x1][j]]=p[i].w;
            per[x1]=x2;
            for(int j=0; j<len_x1; j++)
                G[x2].push_back(G[x1][j]);
        }
    }
    int cisum=INF;
    for(int i=0; i<m; i++)
        if(!p[i].vis)
            cisum=min(cisum,sum+p[i].w-maxd[p[i].u][p[i].v]);
    if(cisum>sum)
        printf("%d\n",sum);
    else
        printf("Not Unique!\n");
}
int main()
{
    int T;
    scanf("%d\n",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0; i<m; i++)
        {
            scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w);
            p[i].vis = false;
        }
        kruskal();
    }
    return 0;
}

终 THE END

Stephen Zeng

文章作者

发表评论

textsms
account_circle
email

Stephen Horizon

『OI笔记』次小生成树
前置芝士 一点点图论芝士 Kruskal(或者你用Prim也行),用来求最小生成树 定义   定义就是字面意思,无需解释。 思路   先将输入的图的最小生成树求出来,再枚举没有增…
扫描二维码继续阅读
2021-09-26