题目大意:
求一个图的最小生成树是否唯一。
现有t个测例(1 ≤ t ≤ 20),每个测例中会给出点数n和边数m(1 ≤ n ≤ 100,边数未知),接下来给出m条边的信息,形式诸如"1 2 1",表示点1和点2有边相连,边权值为1,所有点编号为1 ~ n,对于每个测例,如果最小生成树唯一则输出其总边权值,如果不唯一则输出"Not Unique!"。
背景:
次小生成树就是所有最小生成树的集合(一个图可以有多个最小生成树,这些最小生成树之间形态不同,但是把它们边都从小到大排列每条边的权值都对应相等),一棵最小生成树可以通过另一颗最小生成树换一条边得到(因为最小生成树中如果断掉任意一条边都会形成两个连通分量,并且这两个连通分量断开了,可以在这两个连通分量中各找一个点将它们俩相连就又可以形成一棵生成树,如果这两点形成的边和被断掉的边权值相同并且这两点不全是被断掉的边的两点的话就又形成了一棵不同的最小生成树了。因此,此题的算法就是求次小生成树,具体步骤就是:
1. 用Prim+堆优化求出最小生成树;
2. 在求最小生成树的同时维护一组数据max_seg[i][j],表示最小生成树上点i到点j路径上的最大边的权值(最小生成树上两点之间的路径是唯一的!!)
*. 在Prim中每加进来一个点u就可以求出树中其它已加进来的点到u的路径上的最大边权值:max_seg[i][u] = max( max_seg[i][ pre[u] ], g[ pre[u] ][u] ),其中i为已被加进来的点(不包括u),pre[u]为已加进来的点中与u相连的那个点,由于该过程是伴随Prim,g[i][j]为边(i, j)的权值,g表示graph,因此Prim的复杂度仍然为ElogV;
3. 枚举不在最小生成树中的每条边(i, j),如果有g[i][j]等于max_seg[i][j]的情况就表示最小生成树不唯一(g[i][j]必定大于等于max_seg[i][j],否则就存在比最小生成树还小的生成树了,因此总复杂度为ElogV + E。
注释代码:
/*
* Problem ID : POJ 1679 The Unique MST
* Author : Lirx.t.Una
* Language : C++
* Run Time : 16 ms
* Run Memory : 260 KB
*/
#include <iostream>
#include <climits>
#include <cstring>
#include <cstdio>
#include <queue>
//最大点数
#define MAXN 100
using namespace std;
struct Node {//堆优化中的结点
int u;//点的序号
int d;//当前点到生成树的最短距离
Node(void) {}
Node( int uu, int dd ) : u(uu), d(dd) {}
bool//小顶堆
operator<(const Node &oth)
const {
return d > oth.d;
}
};
int max_seg[MAXN + 1][MAXN + 1];//max_seg[i][j]表示当前最小生成树中i到j路径中的最大边的边权
int g[MAXN + 1][MAXN + 1];//图,表示边的权值
int d[MAXN + 1];//d[i]表示Prim中点i到最小生成树的当前最短距离
int pre[MAXN + 1];//pre[i]表示i的父结点(即与之相连的点)
bool vis[MAXN + 1];//Prim中表示点是否被访问过
bool mst[MAXN + 1][MAXN + 1];//mst[i][j]表示边(i, j)是否为最小生成树的点
inline int
max( int a, int b ) {
return a > b ? a : b;
}
int
prim(int n) {
int i;
int u, v;
int nv;//number of vertices,最小生成树中的当前点数
int ans;//最小生成树的总边权
Node node;
priority_queue<Node> heap;
//初始化
memset(vis, 0, sizeof(vis));
memset(mst, 0, sizeof(mst));
memset(max_seg, -1, sizeof(max_seg));
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
pre[i] = 1;
heap.push(Node( i, d[i] ));
}
ans = 0;//初始化
nv = 1;//1已经被纳入
while ( nv < n && !heap.empty() ) {
//nv < n测试是否已经生成最小生成树
//!heap.empty()测试在未生成时就已经无点可用,测试最小生成树是否存在
while ( !heap.empty() ) {//也表示最小生成树是否存在
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {//不能重复
nv++;//被纳入一个点,到最小生成树距离最小
ans += node.d;//边权累加
//边被计入最小生成树中
mst[ pre[u] ][u] = true;
mst[u][ pre[u] ] = true;
for ( i = 1; i <= n; i++ )//更新max_seg
if ( vis[i] ) {
max_seg[i][u] = max( max_seg[i][ pre[u] ], node.d );
max_seg[u][i] = max_seg[i][u];
}
vis[u] = true;//放到最后再更新,避免在更新max_seg时无谓地更新max_seg[u][u],没有意义
break;
}
}
if ( nv == n ) break;//表示已经完成
for ( v = 2; v <= n; v++ )//跟新d[i]
if ( !vis[v] && g[u][v] < d[v] ) {
d[v] = g[u][v];
pre[v] = u;
heap.push(Node( v, d[v] ));
}
}
return ans;
}
int
main() {
int t;//测例数
int n, m;//点数和边数
int i, j;//临时点
int w;//临时边权值
int ans;//最小生成树的总边权
bool not_unique;//标记是否MST唯一
scanf("%d", &t);
while ( t-- ) {
scanf("%d%d", &n, &m);
for ( i = 1; i <= n; i++ )//初始化
for ( j = i + 1; j <= n; j++ )
g[i][j] = g[j][i] = INT_MAX;
while ( m-- ) {
scanf("%d%d%d", &i, &j, &w);
g[i][j] = g[j][i] = w;
}
ans = prim(n);
not_unique = false;
for ( i = 1; i <= n; i++ ) {
for ( j = i + 1; j <= n; j++ )
if ( !mst[i][j] && g[i][j] == max_seg[i][j] ) {//测试是否可以替换
not_unique = true;
break;
}
if ( not_unique ) break;
}
if ( not_unique ) puts("Not Unique!");
else printf("%d\n", ans);
}
return 0;
}
无注释代码:
#include <iostream>
#include <climits>
#include <cstring>
#include <cstdio>
#include <queue>
#define MAXN 100
using namespace std;
struct Node {
int u;
int d;
Node(void) {}
Node( int uu, int dd ) : u(uu), d(dd) {}
bool
operator<(const Node &oth)
const {
return d > oth.d;
}
};
int max_seg[MAXN + 1][MAXN + 1];
int g[MAXN + 1][MAXN + 1];
int d[MAXN + 1];
int pre[MAXN + 1];
bool vis[MAXN + 1];
bool mst[MAXN + 1][MAXN + 1];
inline int
max( int a, int b ) {
return a > b ? a : b;
}
int
prim(int n) {
int i;
int u, v;
int nv;
int ans;
Node node;
priority_queue<Node> heap;
memset(vis, 0, sizeof(vis));
memset(mst, 0, sizeof(mst));
memset(max_seg, -1, sizeof(max_seg));
vis[1] = true;
for ( i = 2; i <= n; i++ ) {
d[i] = g[1][i];
pre[i] = 1;
heap.push(Node( i, d[i] ));
}
ans = 0;
nv = 1;
while ( nv < n && !heap.empty() ) {
while ( !heap.empty() ) {
node = heap.top();
heap.pop();
if ( !vis[ u = node.u ] ) {
nv++;
ans += node.d;
mst[ pre[u] ][u] = true;
mst[u][ pre[u] ] = true;
for ( i = 1; i <= n; i++ )
if ( vis[i] ) {
max_seg[i][u] = max( max_seg[i][ pre[u] ], node.d );
max_seg[u][i] = max_seg[i][u];
}
vis[u] = true;
break;
}
}
if ( nv == n ) break;
for ( v = 2; v <= n; v++ )
if ( !vis[v] && g[u][v] < d[v] ) {
d[v] = g[u][v];
pre[v] = u;
heap.push(Node( v, d[v] ));
}
}
return ans;
}
int
main() {
int t;
int n, m;
int i, j;
int w;
int ans;
bool not_unique;
scanf("%d", &t);
while ( t-- ) {
scanf("%d%d", &n, &m);
for ( i = 1; i <= n; i++ )
for ( j = i + 1; j <= n; j++ )
g[i][j] = g[j][i] = INT_MAX;
while ( m-- ) {
scanf("%d%d%d", &i, &j, &w);
g[i][j] = g[j][i] = w;
}
ans = prim(n);
not_unique = false;
for ( i = 1; i <= n; i++ ) {
for ( j = i + 1; j <= n; j++ )
if ( !mst[i][j] && g[i][j] == max_seg[i][j] ) {
not_unique = true;
break;
}
if ( not_unique ) break;
}
if ( not_unique ) puts("Not Unique!");
else printf("%d\n", ans);
}
return 0;
}
单词解释:
acyclic:adj, 非循环的,非周期的
connected:adj, 连通的
subgraph:n, 子图
spanning:n, 生成,跨越(数学)
spanning tree:n, 生成树
indirect:adj, 间接的
purchasing:n, 采购
purchase:vt, 采购
yield:vt, 产出
power:n, 功率
regardless of:vt, 不管,不顾
via:prep, 通过,经由
channel:n, 通道,频道
satellite:n, 卫星
in addition:adv, 另外,此外
transceiver:n, 无线电收发机
radio:n, 收音机,无线电设备
outpost:n, 前哨,警戒部队
be short of/on:vt, 缺乏
toll:n, 通行费
one-way:adj, 单程的,单行的,单向的