次小生成树题目及思路 poj1679

版权声明:希望能在自己成长的道路上帮到更多的人,欢迎各位评论交流 https://blog.csdn.net/yiqzq/article/details/79959373

下面只是讲讲我对次小生成树一些理解,推荐先去看一下次小生成树的一些概念再来看这篇博客。

首先我们要明确一点,在最小生成树中,任意两个点之间有且仅有一条路径。然后是次小生成树由何而来,必然是最小生成树转变而来。如何转变,应该是将一条不是最小生成树中的边,替换最小生成树的一条边,且替换后任意节点之间还应该有通路。那么思路就来了。由于最小生成树的特点,因此往树中添加一条边,就必然会形成一个环,那么去掉的边也应该是环上属于最小生成树的某一条边。大体思路就是这个样子。

1.先形成最小生成树,同时对于需要标记属于最小生成树的边,下面代码用vis来标记。

2.在每次选取一个离生成树最近的一个点后,需要枚举所有已经加入最小生成树的点,依次更新Max数组,Max[i][j]表示在i节点和j节点的路径上最小生成树的最大权值(因为在最后枚举不是最小生成树的边的时候,显然去掉环上权值最大的边才能使次小生成树的总值尽可能的小)。而Max[i][j]的更新是dp的思想,建议好好理解。

3.最后就是枚举所有不在最小生成树上的边,依次更新次小生成树。

下面的代码是poj1679的题。
就是询问所给出的数据所形成的最小生成树是否为一。
如何判断唯一,只需要判断次小生成树是否和最小生成树相等就行了。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int maxn = 105;
int e[maxn][maxn];//图
int vis[maxn][maxn];//标记边
int dis[maxn];//原本是距离数组,但是因为是求最小生成树,因此也可以
int pre[maxn];//前驱节点
int Max[maxn][maxn];//记录最大权值的边
int t, n, m;
int sum;//最小生成树
int minsum;//次小生成树


//bool cmp(pii a,pii b){
//return a.second<b.second;
//}


void prim() {
    priority_queue<pii, vector<pii>, greater<pii> >q;
    //这里求大佬解答,一开始重载了pair的小于号,就是上面所注释掉的语句,但是好像没效果
    dis[1] = 0;
    q.push(pii(0, 1));//距离,节点
    while(!q.empty()) {
        int u = q.top().first;//u是距离
        int v = q.top().second;//v是节点
        q.pop();
        if(dis[v] < u) continue;//如果当前节点是树中节点就返回
        sum += dis[v];
        dis[v] = -1;//一旦已经是树中集合,就记为-1
        if(pre[v] > 0) {
            vis[v][pre[v]] = vis[pre[v]][v] = 1;//pre[i]=j,表示i的前驱节点是j
        }
        for(int i = 1; i <= n; i++) {
            if(i == v) continue; //不知道为什么不可以相等
            if(dis[i] < 0) { //如果已经是树中节点就要更新Max
                Max[i][v] = Max[v][i] = max(Max[pre[v]][i], e[v][pre[v]]);//这里有dp的思想,是核心
            } else if(dis[i] > e[i][v]) {//普通的prim更新操作,多了pre数组的更新
                dis[i] = e[i][v];
                pre[i] = v;
                q.push(pii(dis[i], i));
            }
        }
    }
}
void solve() {
    minsum = inf;
    for(int i = 1; i <= n; i++) {//遍历每一条不在最小生成树的边
        for(int j = 1 + 1; j <= n; j++) { //因为是双向图,所以只要遍历一半图就行了
            if(!vis[i][j]) {//如果不在就更新minsum
                minsum = min(minsum, sum - Max[i][j] + e[i][j]);
            }
        }
    }
    if(minsum == sum) {
        printf("Not Unique!\n");
    } else printf("%d\n", sum);
}
int main() {
    scanf("%d", &t);
    while(t--) {
        sum = 0;
        memset(dis, inf, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        memset(pre, -1, sizeof(pre));
        memset(e, inf, sizeof(e));
        memset(Max, 0, sizeof(Max));
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            e[u][v] = w;//双向
            e[v][u] = w;
        }
        prim();
        solve();
    }
    return 0;
}

阅读更多

没有更多推荐了,返回首页