【POJ1679 The Unique MST】 非严格次小生成树模板

一道次小生成树的模板题。

题意

给定一个无向连通图,判断最小生成树是否唯一。唯一的话输出最小生成树的总权值。

思路

我们只要分别求出图的最小生成树和非严格次小生成树,然后判断它们是否相等就好。

次小生成树算法解释

次小生成树前置知识:最小生成树、LCA
我们先求出图G的最小生成树,之后再找出不包含在最小生成树的边。每一条这样的边e(u,v),将它加入到最小生成树中,由树的定义,我们很容易想到这样会形成一个环,而这个环路即是(u>v>lca>u)。
由于除去边e外,环路上的每条边必然小于等于e(因为是最小生成树),因此我们只要找到环路上除e外的最大边,即u>lca和v>lca两条路径上的最大边,减去这条最大边的权值再加上e的权值,新生成的树的权值必然大于等于最小生成树,并且与最小生成树的差值仅为这两条边的差。那么我们对每一条非树边进行这样的操作后,新生成的树中权值最小的那个,就一定是图G的次小生成树。
前面我们说到,找环路上的最大边,即是找u>lca和v>lca两条路径上的最大边。因此我们可以用树上倍增的做法,用一个数组max_dis[x][i]来储存x到第2^i个父节点上的最大边。这样我们就可以在计算lca的同时求出环路上的最大边。

模板

#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <functional>
using namespace std;

typedef long long ll;

const int MAXN = 100005;
const double EPS = 1e-8;
const int INF = 1e9+7;

const int NODE_MAXN = 10005; //点的最大数量
const int EDGE_MAXN = 10005; //边的最大数量
const int FA_MAXN = 20;

typedef struct edge{
    int u;
    int v;
    int k; //边的权值
    int vis;
} edge; //边

int fa[NODE_MAXN][FA_MAXN]; //储存第2^i个父节点
int max_dis[NODE_MAXN][FA_MAXN]; //储存到第2^i个节点的最大边权
int set_fa[NODE_MAXN]; //用于并查集
vector<int> g[NODE_MAXN]; //图
int dep[NODE_MAXN]; //深度
edge e[EDGE_MAXN]; //存边

void add_edge(int k){ //记录第k条边
    e[k].vis = 1;
    g[e[k].u].push_back(k);
    g[e[k].v].push_back(k);
}

bool edge_cmp(edge a,edge b){
    return a.k < b.k;
}

int set_find(int x){
    if(set_fa[x]!= x) set_fa[x]=set_find(set_fa[x]);
    return set_fa[x];
}

void set_union(int x,int y){
    x=set_find(x);
    y=set_find(y);
    set_fa[y]=x;
}

int kruskla(int n,int m){ //用kruskla算法求出最小生成树
    for (int i = 1; i <= n;i++){
        set_fa[i] = i; 
    }
    sort(e + 1, e + m + 1, edge_cmp);
    int sum = 0;
    for (int i = 1; i <= m;i++){
        if(set_find(e[i].u)!=set_find(e[i].v)){
            set_union(e[i].u, e[i].v);
            sum += e[i].k;
            add_edge(i); //记录这条边
        }
    }
    for (int i = 2; i <= n;i++){
        if(set_find(i)!=set_find(i-1)){
            return -1;
        }
    }
    return sum;
}

void fa_dfs(int k){ //dfs每个点的第一个父亲
    for (int i = 0; i < g[k].size();i++){
        if(e[g[k][i]].v==k) swap(e[g[k][i]].u, e[g[k][i]].v);
        if(!dep[e[g[k][i]].v]){
            dep[e[g[k][i]].v] = dep[k] + 1; //深度+1
            fa[e[g[k][i]].v][0] = k; //记录第一个父节点
            max_dis[e[g[k][i]].v][0] = e[g[k][i]].k; //与第一个父节点的距离
            fa_dfs(e[g[k][i]].v);
        }
    }
}

void fa_build(int n){ //输入点的数量,建立fa数组
    for (int i = 1; i < FA_MAXN;i++){
        for (int j = 1; j <= n;j++){
            if(fa[j][i - 1]==-1) continue;
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
            max_dis[j][i] = max(max_dis[j][i - 1], max_dis[fa[j][i - 1]][i - 1]);
        }
    }
}

int LCA_maxdis(int a,int b){ //求a,b到最近公共祖先的最大边
    if(dep[a]<dep[b]) swap(a, b);
    int max_dis1 = 0, max_dis2 = 0;
    for (int i = FA_MAXN - 1; i >= 0;i--){
        if(fa[a][i]==-1) continue;
        if(dep[fa[a][i]]>=dep[b]) {
            max_dis1 = max(max_dis1, max_dis[a][i]);
            a = fa[a][i];
        }
    }
    if(a==b) return max_dis1;
    for (int i = FA_MAXN - 1; i >= 0;i--){
        if(fa[a][i]==-1||fa[b][i]==-1) continue;
        if(fa[a][i]!=fa[b][i]){
            max_dis1 = max(max_dis1, max_dis[a][i]);
            max_dis2 = max(max_dis2, max_dis[b][i]);
            a = fa[a][i]; b = fa[b][i];
        }
    }
    max_dis1 = max(max_dis1, max_dis[a][0]);
    max_dis2 = max(max_dis2, max_dis[b][0]);
    return max(max_dis1, max_dis2);
}

int second_MST(int n,int m){ //n是点数,m是边数
    int ans = INF; //将次小生成树的权值初始化为无穷大
    for (int i = 1; i <= n;i++){
        dep[i] = 0;
        g[i].clear();
        for (int j = 0; j < FA_MAXN;j++){
            fa[i][j] = -1;
        }
    }
    for (int i = 1; i <= m;i++){
        e[i].vis = 0;
    }
    int sum = kruskla(n, m); //求最小生成树
    if(sum==-1) return -1; //无法生成最小生成树,也就没有次小生成树
    int root = 1;
    dep[root] = 1;
    fa_dfs(root);
    fa_build(n);
    int flag = -1;
    for (int i = 1; i <= m;i++){
        if(e[i].vis) continue;
        int lca = LCA_maxdis(e[i].u, e[i].v);
        if(sum+e[i].k-lca<ans){
            ans = sum + e[i].k - lca;
            flag = i;
        }
    }
    if(flag==-1) return -1;
    return ans;
}

int main(){
    int n, m;
    while(~scanf("%d %d", &n, &m)){
        for (int i = 1; i <= m;i++){
            scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].k);
        }
        int ans = second_MST(n, m);
        if(ans!=-1) printf("%d\n", ans);
    }
    return 0;
}

AC代码

#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <functional>
using namespace std;

typedef long long ll;

const int MAXN = 100005;
const double EPS = 1e-8;
const int INF = 1e9+7;

const int NODE_MAXN = 1005; //点的最大数量
const int EDGE_MAXN = 1005; //边的最大数量
const int FA_MAXN = 20;

typedef struct edge{
    int u;
    int v;
    int k; //边的权值
    int vis;
} edge; //边

int fa[NODE_MAXN][FA_MAXN]; //储存第2^i个父节点
int max_dis[NODE_MAXN][FA_MAXN]; //储存到第2^i个节点的最大边权
int set_fa[NODE_MAXN]; //用于并查集
vector<int> g[NODE_MAXN]; //图
int dep[NODE_MAXN]; //深度
edge e[EDGE_MAXN]; //存边

void add_edge(int k){ //记录第k条边
    e[k].vis = 1;
    g[e[k].u].push_back(k);
    g[e[k].v].push_back(k);
}

bool edge_cmp(edge a,edge b){
    return a.k < b.k;
}

int set_find(int x){
    if(set_fa[x]!= x) set_fa[x]=set_find(set_fa[x]);
    return set_fa[x];
}

void set_union(int x,int y){
    x=set_find(x);
    y=set_find(y);
    set_fa[y]=x;
}

int kruskla(int n,int m){ //用kruskla算法求出最小生成树
    for (int i = 1; i <= n;i++){
        set_fa[i] = i; 
    }
    sort(e + 1, e + m + 1, edge_cmp);
    int sum = 0;
    for (int i = 1; i <= m;i++){
        if(set_find(e[i].u)!=set_find(e[i].v)){
            set_union(e[i].u, e[i].v);
            sum += e[i].k;
            add_edge(i); //记录这条边
        }
    }
    for (int i = 2; i <= n;i++){
        if(set_find(i)!=set_find(i-1)){
            return -1;
        }
    }
    return sum;
}

void fa_dfs(int k){ //dfs每个点的第一个父亲
    for (int i = 0; i < g[k].size();i++){
        if(e[g[k][i]].v==k) swap(e[g[k][i]].u, e[g[k][i]].v);
        if(!dep[e[g[k][i]].v]){
            dep[e[g[k][i]].v] = dep[k] + 1; //深度+1
            fa[e[g[k][i]].v][0] = k; //记录第一个父节点
            max_dis[e[g[k][i]].v][0] = e[g[k][i]].k; //与第一个父节点的距离
            fa_dfs(e[g[k][i]].v);
        }
    }
}

void fa_build(int n){ //输入点的数量,建立fa数组
    for (int i = 1; i < FA_MAXN;i++){
        for (int j = 1; j <= n;j++){
            if(fa[j][i - 1]==-1) continue;
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
            max_dis[j][i] = max(max_dis[j][i - 1], max_dis[fa[j][i - 1]][i - 1]);
        }
    }
}

int LCA_maxdis(int a,int b){ //求a,b到最近公共祖先的最大边
    if(dep[a]<dep[b]) swap(a, b);
    int max_dis1 = 0, max_dis2 = 0;
    for (int i = FA_MAXN - 1; i >= 0;i--){
        if(fa[a][i]==-1) continue;
        if(dep[fa[a][i]]>=dep[b]) {
            max_dis1 = max(max_dis1, max_dis[a][i]);
            a = fa[a][i];
        }
    }
    if(a==b) return max_dis1;
    for (int i = FA_MAXN - 1; i >= 0;i--){
        if(fa[a][i]==-1||fa[b][i]==-1) continue;
        if(fa[a][i]!=fa[b][i]){
            max_dis1 = max(max_dis1, max_dis[a][i]);
            max_dis2 = max(max_dis2, max_dis[b][i]);
            a = fa[a][i]; b = fa[b][i];
        }
    }
    max_dis1 = max(max_dis1, max_dis[a][0]);
    max_dis2 = max(max_dis2, max_dis[b][0]);
    return max(max_dis1, max_dis2);
}

int second_MST(int n,int m){ //n是点数,m是边数
    int ans = INF; //将次小生成树的权值初始化为无穷大
    for (int i = 1; i <= n;i++){
        dep[i] = 0;
        g[i].clear();
        for (int j = 0; j < FA_MAXN;j++){
            fa[i][j] = -1;
        }
    }
    for (int i = 1; i <= m;i++){
        e[i].vis = 0;
    }
    int sum = kruskla(n, m); //求最小生成树
    if(sum==-1) return -1;
    int root = 1;
    dep[root] = 1;
    fa_dfs(root);
    fa_build(n);
    int flag = -1;
    for (int i = 1; i <= m;i++){
        if(e[i].vis) continue;
        int lca = LCA_maxdis(e[i].u, e[i].v);
        if(sum+e[i].k-lca<ans){
            ans = sum + e[i].k - lca;
            flag = i;
        }
    }
    if(ans==sum)
        return -1; //最小生成树和次小生成树不相等
    return sum; //由题意,这里要求出最小生成树的权值
}

int main(){
    int t; scanf("%d", &t);
    while(t--){
        int n, m;
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= m;i++){
            scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].k);
        }
        int ans = second_MST(n, m);
        if(ans!=-1) printf("%d\n", ans);
        else printf("Not Unique!\n");
    }
    return 0;
}
posted @ 2021-01-27 16:13  樱与梅子  阅读(111)  评论(0)    收藏  举报