题目链接:[POJ 3164]Command Network[最小树形图]
题意分析:
求从根节点出发,到达所有其他节点的边所构成权值最小的生成子图权值是多少? (单向边)
解题思路:
无向图的最小生成树再熟悉不过了,这次变成了有向图上固定结点的最小生成树,引入一个算法:朱-刘算法,没错,就是这个名字,Made In China~。
首先看看《挑战程序设计》对最小树形图的定义:
具体算法流程就不描述了,写在代码里了。
这着重说说两点:
- 为什么可以依靠结点是否没有入边来判断是否存在最小树形图?(可能有人会认为,存在一种样例,根节点没有指向其他结点的出边而只有入边,其他点均有入边)——回答: 对哒,确实存在。此时不会判定不存在,但是其他点一定构成了环(画图看看)。而后期的缩点,不断重复过程,最终这组样例会出现没有环的情况,而此时,就会存在点没有入边,返回-1。
- 为什么新图中需要对edge[i].w进行减法?——-回答:这样在选择了这条边后,减去了这个节点在原来图中的入边权值,间接等价于选择了这条边放弃了那条原来的边。
个人感受:
算了入了点门了2333
具体代码如下:
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 0x7f7f7f7f;
const int MAXN = 111;
struct Edge{
int u, v;
double w;
}edge[MAXN * MAXN];
int n, m, pre[MAXN], newid[MAXN], vis[MAXN];
double x[MAXN], y[MAXN], in[MAXN];
double getdis(int a, int b)
{
double delx = x[a] - x[b], dely = y[a] - y[b];
return sqrt(delx * delx + dely * dely);
}
double zhuLiu(int rt)
{
double ret = 0;
while (1)
{
for (int i = 0; i < n; ++i) in[i] = INF;
// 第一步:找出每个点的最小权入边
for (int i = 0; i < m; ++i)
{
int u = edge[i].u, v = edge[i].v;
if (in[v] > edge[i].w && u != v) in[v] = edge[i].w, pre[v] = u;
}
for (int i = 0; i < n; ++i)
{
if (i == rt) continue;
if (in[i] == INF) return -1; // 判断是否无法构成最小树形图
}
// 第二步:判环
int cnt = 0;
memset(newid, -1, sizeof newid);
memset(vis, -1, sizeof vis);
in[rt] = 0;
for (int i = 0; i < n; ++i)
{
ret += in[i];
int v = i;
while (vis[v] != i && newid[v] == -1 && v != rt) // 找环。vis[]用来标记点在哪个点为首的环中
{
vis[v] = i;
v = pre[v];
}
if (v != rt && newid[v] == -1) // 找到环了,缩点
{
for (int u = pre[v]; u != v; u = pre[u]) newid[u] = cnt;
newid[v] = cnt++;
}
}
if (cnt == 0) break; // 没有环
for (int i = 0; i < n; ++i) // 重新赋予其他点标号
if (newid[i] == -1) newid[i] = cnt++;
for (int i = 0; i < m; ++i) // 建立新图
{
int u = edge[i].u, v = edge[i].v;
edge[i].u = newid[u];
edge[i].v = newid[v];
if (newid[u] != newid[v]) edge[i].w -= in[v]; // 选择当前边的同时便放弃了原来的最小入边.原来的已经加到ret中了
}
n = cnt;
rt = newid[rt];
}
return ret;
}
int main()
{
while (~scanf("%d%d", &n, &m))
{
for (int i = 0; i < n; ++i) scanf("%lf%lf", &x[i], &y[i]);
for (int i = 0; i < m; ++i)
{
scanf("%d%d", &edge[i].u, &edge[i].v);
--edge[i].u;
--edge[i].v;
edge[i].w = getdis(edge[i].u, edge[i].v);
}
double ans = zhuLiu(0);
if (ans != -1) printf("%.2f\n", ans);
else printf("poor snoopy\n");
}
return 0;
}