定义:一个有点图,存在从某个点开始的到达所有的的一个最小生成树,则它就是最小树形图。
View Code
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
View Code
从早晨到现在一直在翻资料,终于理解了一点。朱-刘算法的大概过程如下:
1、找到除了root以为其他点的权值最小的入边。用In[i]记录
2、如果出现除了root以为存在其他孤立的点,则不存在最小树形图。
3、找到图中所有的环,并对环进行缩点,重新编号。
4、更新其他点到环上的点的距离,如:
环中的点有(Vk1,Vk2,… ,Vki)总共i个,用缩成的点叫Vk替代,则在压缩后的图中,其他所有不在环中点v到Vk的距离定义如下:
gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)
而Vk到v的距离为
gh[Vk][v]=min { gh[Vkj][v] } (1<=j<=i)
5、重复3,4知道没有环为止。
如下图所示:
算法过程很简单,不过用到好多拍代码的技巧。。。。在HH那找到一个不错的模板 POJ 3164:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #include <sstream> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) x < y ? x : y #define Max(x, y) x < y ? y : x #define E(x) (1 << (x)) const double eps = 1e-6; const double inf = ~0u>>1; typedef long long LL; using namespace std; const int N = 110; const int M = 10010; struct node { double x, y; } point[N]; struct edg { int u, v; double cost; } E[M]; double In[N]; int ID[N]; int vis[N]; int pre[N]; int NV, NE; double SQ(int u, int v) { return sqrt((point[u].x - point[v].x)*(point[u].x - point[v].x) + (point[u].y - point[v].y)*(point[u].y - point[v].y)); } double Directed_MST(int root) { double ret = 0; int i, u, v; while(true) { REP(i, NV) In[i] = inf; REP(i, NE) { //找最小入边 u = E[i].u; v = E[i].v; if(E[i].cost < In[v] && u != v) { In[v] = E[i].cost; pre[v] = u; } } REP(i, NV) { //如果存在除root以外的孤立点,则不存在最小树形图 if(i == root) continue; //printf("%.3lf ", In[i]); if(In[i] == inf) return -1; } int cnt = 0; CL(ID, -1); CL(vis, -1); In[root] = 0; REP(i, NV) { //找环 ret += In[i]; int v = i; while(vis[v] != i && ID[v] == -1 && v != root) { vis[v] = i; v = pre[v]; } if(v != root && ID[v] == -1) { //重新标号 for(u = pre[v]; u != v; u = pre[u]) { ID[u] = cnt; } ID[v] = cnt++; } } if(cnt == 0) break; REP(i, NV) { if(ID[i] == -1) ID[i] = cnt++; //重新标号 } REP(i, NE) { //更新其他点到环的距离 v = E[i].v; E[i].u = ID[E[i].u]; E[i].v = ID[E[i].v]; if(E[i].u != E[i].v) { E[i].cost -= In[v]; } } NV = cnt; root = ID[root]; } return ret; } int main() { //freopen("data.in", "r", stdin); ... }
面说的是定根的情况,如果是不定根的情况我们可以虚拟一个根,让虚拟根到每个节点的距离为图上所有边的权值之和加一。这样找到最小树形图后再减掉所有边的权值之和加一就可以了。
比如HUD 2121
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #include <sstream> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) x < y ? x : y #define Max(x, y) x < y ? y : x #define E(x) (1 << (x)) const int eps = 1e-6; const int inf = ~0u>>1; typedef long long LL; using namespace std; const int N = 1024; const int M = N*N; struct edg { int u, v; int cost; } E[M]; int In[N]; int ID[N]; int vis[N]; int pre[N]; int NV, NE; int Minroot; int Directed_MST(int root) { int ret = 0; int i, u, v; while(true) { REP(i, NV) In[i] = inf; REP(i, NE) { //找最小入边 u = E[i].u; v = E[i].v; if(E[i].cost < In[v] && u != v) { In[v] = E[i].cost; if(u == root) Minroot = i; //不能直接等于v,因为会缩边 pre[v] = u; } } REP(i, NV) { //如果存在除root以外的孤立点,则不存在最小树形图 if(i == root) continue; if(In[i] == inf) return -1; } int cnt = 0; CL(ID, -1); CL(vis, -1); In[root] = 0; REP(i, NV) { //找环 ret += In[i]; int v = i; while(vis[v] != i && ID[v] == -1 && v != root) { vis[v] = i; v = pre[v]; } if(v != root && ID[v] == -1) { //重新标号 for(u = pre[v]; u != v; u = pre[u]) { ID[u] = cnt; } ID[v] = cnt++; } } if(cnt == 0) break; REP(i, NV) { if(ID[i] == -1) ID[i] = cnt++; //重新标号 } REP(i, NE) { //更新其他点到环的距离 v = E[i].v; E[i].u = ID[E[i].u]; E[i].v = ID[E[i].v]; if(E[i].u != E[i].v) { E[i].cost -= In[v]; } } NV = cnt; root = ID[root]; } return ret; } int main() { //freopen("data.in", "r", stdin); int i, l, x; while(~scanf("%d%d", &NV, &NE)) { l = 0; x = NE; REP(i, NE) { scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].cost); l += E[i].cost; } l++; REP(i, NV) { E[NE].u = NV; E[NE].v = i; E[NE].cost = l; NE++; } NV++; int ans = Directed_MST(NV-1); if(ans == -1 || ans >= 2*l) puts("impossible"); else printf("%d %d\n", ans - l, Minroot - x); cout << endl; } return 0; }