最小生成树-->次小生成树

一直以为次小生成树的权值之和应该比最小生成树的权值和要大,今天看了次小生成树的得到过程,原来如果一个图中存在多个最小生成树,那么次小生成树的权值和和最小生成树的权值和是一样大的。

结论:次小生成树可由最小生成树换一条边得到。

证明:T是某一棵最小生成树,T0是一棵异于T的树,通过变换T0-->T1-->T2-->...-->Tn(T)变成最小生成树,所谓的变换, 是每次把Ti中的某条边换成T中的一条边,而且Ti+1的权小于等于Ti的权。

具体操作是:

step1.在Ti中任取一条不在T中的边u_v.

ster 2.把边u_v去掉,就剩下两个连通分量A和B,在T中,必有唯一的边u`_v`连接A和B.

step3.显然u`_v`的权比u_v小(否则u_v就应该在T中),把u`_v`替换u_v既得到树Ti+1;

特别的:取Tn为任一棵最小生成树,Tn-1也就是次小生成树且跟T差一条边,结论得证。

算法:只要充分利用以上结论,既得V^2的算法。具体如下:

step 1. 先用prim算法求出最小生成树T, 在prim的同时,用一个矩阵max[u][v]记录在T中连接任意两点u、v的唯一的路中权值最大的那条边的权值。具体实现:在prim中每次增加一个结点v, 记录得到结点v的父亲结点father[v],设已经加入最小生成树中的结点集合为w,则w中所有的结点到v中的最大权值为

max(新加入的边的权值,父亲结点到集合w中任意结点的权值)

step 2.枚举所有不在T中的边u_v,加入边u_v替换权为max[u][v]的边。不断更新求最小值(u_v与max[u][v]的差值),既得次小生成树。

实战:

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=118

修路方案

时间限制: 3000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军率领着许多部队,它们分别驻扎在N个不同的城市里,这些城市分别编号1~N,由于交通不太便利,南将军准备修路。

现在已经知道哪些城市之间可以修路,如果修路,花费是多少。

现在,军师小工已经找到了一种修路的方案,能够使各个城市都联通起来,而且花费最少。

但是,南将军说,这个修路方案所拼成的图案很不吉利,想让小工计算一下是否存在另外一种方案花费和刚才的方案一样,现在你来帮小工写一个程序算一下吧。

输入
第一行输入一个整数T(1<T<20),表示测试数据的组数
每组测试数据的第一行是两个整数V,E,(3<V<500,10<E<200000)分别表示城市的个数和城市之间路的条数。数据保证所有的城市都有路相连。
随后的E行,每行有三个数字A B L,表示A号城市与B号城市之间修路花费为L。
输出
对于每组测试数据输出Yes或No(如果存在两种以上的最小花费方案则输出Yes,如果最小花费的方案只有一种,则输出No)
样例输入
2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2
样例输出
No
Yes
题目分析:先求出最小生成树的权值和,再求出次小生成树的权值和,如果两个权值和相等,则说明图中至少有两个最小生成树,否则只有一个最小生成树。
#include <cstdio>
#include <cstring>
#define INF 0x3f3f3f3f
#define V 505

int max(int a, int b) {
    return a > b ? a : b;
}

int vis[V], lowc[V], father[V];  //fahter[i]记录当第i个点加入时的父亲结点
int cost[V][V], n;
int maxd[V][V];      //最小生成树中任意两点之间的路径上的权值最大的边的权值
int que[V], len;

int prim() {
    int i, j, p;
    int minc, res = 0;
    len = 0;
    memset(vis, 0, sizeof(vis));
    vis[0] = 1;
    que[len++] = 0;
    for(i=1; i<n; i++) {
        lowc[i] = cost[0][i];
        father[i] = 0;
    }
    for(i=1; i<n; i++) {
        minc = INF; p = -1;
        for(j=0; j<n; j++){
            if(vis[j] == 0 && minc > lowc[j]) {
                minc = lowc[j]; p = j;
            }
        }
        if(INF == minc) return -1;
        res += minc; vis[p] = 1;
        que[len++] = p;  //记录已经加入到最小生成树中的点
        //当有新结点加入到最小生成树中后,更新maxd的值,记录已经存在的任意点到新加入的点之间
        //路径上权值最大的边的权值
        for(j=0; j<len-1; j++) {
            maxd[que[j]][p] = maxd[p][que[j]] = max(maxd[father[p]][que[j]], minc);
            //maxd的值为新加入的边和父亲结点的maxd的最大值
        }
        for(j=0; j<n; j++) {
            if(vis[j] == 0 && lowc[j] > cost[p][j]) {
                lowc[j] = cost[p][j];
                father[j] = p;      //最小生成树上的结点的父亲结点为距离当前结点最近的结点
            }
        }
    }
    return res;
}

int main() {
    int v, e, a, b, l;
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &v, &e);
        n = v;
        for(int i=0; i<v; i++)
        for(int j=0; j<v; j++) {
            maxd[i][j] = -1;
            if(i == j) cost[i][j] = 0;
            else {
                cost[i][j] = INF;
                cost[j][i] = INF;
            }
        }
        for(int i=0; i<e; i++) {
            scanf("%d%d%d", &a, &b, &l);
            if(l < cost[a-1][b-1]) {
                cost[a-1][b-1] = l;
                cost[b-1][a-1] = l;
            }
        }
        int ans;
        ans = prim();
        int flag = 0;
        for(int i=0; i< n; i++) {
            for(int j=0; j<n; j++) {
                if(i == j) continue;
                //i,j之间的边cost[i][j]与i,j之间在最小生成树中路径上权值最大的边的权值比较
                //i,j在最小生成树中不能相邻(father[i]!=j && father[j]!=i),否则maxd[i][j]和
                //cost[i][j]表示的是同一条边
                if(maxd[i][j] == cost[i][j] && father[i]!=j && father[j]!=i) {
                    flag = 1;
                    break;
                }
            }
            if(flag) break;
        }

        if(flag) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值