【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;
}

浙公网安备 33010602011771号