Problem Description
Input
Output
Sample Input
Sample Output
Author
Source
Solution
朱刘算法
这个题目显然是有向图求最小生成树,但是因为没有根节点,所以认为的将所有节点编号+1,从而引入0号根节点
在用朱刘算法求最小生成树的之前,先考虑一下引入的根节点的问题:根节点到每个节点的距离应该为所有边的距离之和再加上1,最后的最小费用应该是用求出来的值减去根节点到每个节点的距离
先考虑找到最小边,其实就是用两点间的距离不断更新到其前驱节点(父节点)的最小距离,同时记录前驱节点,更新到各点距离最小的点(输出时需减去边数)对于存在到期前驱节点的距离为无穷大的点显然不能由根节点遍历到,那么就不存在最小生成树
然后找环,如果该节点通过其父节点能够再次回到自己,那么该节点就在一个环中,将该环全部标记为一个序号,然后不停的递归搜索,直到没有环为止,就求出了最小生成树(break掉)
最后缩点,把环里的所有边的起点和终点都改为其环的编号,并修改环中点到其父节点的距离为减去当前换种距离后的值,对于不在环中的点,按顺序为之标号即可
Code
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <map>
#include <vector>
#include <queue>
#define L 1010
#define LL long long
#define inf 0x7f7f7f7f
using namespace std;
struct edge {
int from, to, w;
} e[L * L];
int n, m, s, t, c, cnt, sum, in[L], pre[L], mr, vis[L], id[L];
inline void add(int a, int b, int v) {
e[cnt].from = a, e[cnt].to = b, e[cnt++].w = v;
}
inline int MST(int root, int N, int E) {
int res = 0;
while (true) {
for (int i = 0; i < N; ++i) in[i] = inf;
memset(pre, -1, sizeof(pre));
for (int i = 0; i < E; ++i) {
int u = e[i].from, v = e[i].to, w = e[i].w;
if (in[v] > w && u != v) {
in[v] = w, pre[v] = u;//*
if (u == root) mr = i;
}
}
for (int i = 0; i < N; ++i) {
if (i == root) continue;
if (in[i] == inf) return -1;
}
int node = 0;
memset(vis, -1, sizeof(vis));
memset(id, -1, sizeof (id));
in[root] = 0;//*
for (int i = 0; i < N; ++i) {
res += in[i];
int v = i;
while (id[v] == -1 && v != root && vis[v] != i) vis[v] = i, v = pre[v];
if (id[v] == -1 && v != root){
for (int u = pre[v]; u != v; u = pre[u]) id[u] = node;
id[v] = node++;
}
}
if (node == 0) break;
for (int i = 0; i < N; ++i) if (id[i] == -1) id[i] = node++;
for (int i = 0; i < E; ++i) {
int u = e[i].from, v = e[i].to;
e[i].from = id[u], e[i].to = id[v];
if (e[i].from != e[i].to) e[i].w -= in[v];
}
N = node, root = id[root];//*
}
return res;
}
int main() {
freopen("HDU2121.in", "r", stdin);
freopen("HDU2121.out", "w", stdout);
while (scanf("%d %d", &n, &m) != EOF) {
cnt = 0, sum = 0;//*
for (int i = 1; i <= m; ++i) {
scanf("%d %d %d", &s, &t, &c);
add(s + 1, t + 1, c);
sum += c;
}
sum++;
for (int i = 1; i <= n; ++i) add(0, i, sum);//*
int ans = MST(0, n + 1, cnt);
if (ans == -1 || ans >= sum * 2) printf("impossible\n\n");//*
else printf("%d %d\n\n", ans - sum, mr - m);//*
}
return 0;
}
Summary
具体写题目时的问题,已在代码中标记出,简单统计一下:
1、每组数据输出后,还要输出一个空行
2、不要忘记根节点到每一个节点的距离
3、每一次递归调用时,更新根节点应更新为其对应的环的标号,而不是mr