[轉載]有向圖的最小生成樹,最小樹形圖
轉載:
有固定根的最小樹形圖求法O(VE):
首先消除自環,顯然自環不在最小樹形圖中。然後判定是否存在最小樹形圖,以根為起點DFS一遍即可。
之後進行以下步驟。
設cost為最小樹形圖總權值。
0.置cost=0。
1.求最短弧集合Ao (一條弧就是一條有向邊)
除源點外,為所有其他節點Vi,找到一條以Vi為終點的邊,把它加入到集合Ao中。
(加邊的方法:所有點到Vi的邊中權值最小的邊即為該加入的邊,記prev[vi]為該邊的起點,mincost[vi]為該邊的權值)
2.檢查Ao中的邊是否會形成有向圈,有則到步驟3,無則到步驟4。
(判斷方法:利用prev陣列,列舉為檢查過的點作為搜尋的起點,做類似DFS的操作)
3.將有向環縮成一個點。
假設環中的點有(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)
同時注意更新prev[v]的值,即if(prev[v]==Vkj) prev[v]=Vk
另外cost=cost+mincost[Vkj] (1<=j<=i)
到步驟1.
4.cost加上Ao的權值和即為最小樹形圖總權值。
如要輸出最小樹形圖較煩,沒實現過。
找環O(V),收縮O(E),總複雜度O(VE)。
對於不固定根的最小樹形圖,wy教主有一巧妙方法。摘錄如下:
新加一個點,和每個點連權相同的邊,這個權大於原圖所有邊權的和,這樣這個圖固定跟的最小樹形圖和原圖不固定跟的最小樹形圖就是對應的了。
程式碼:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define maxn 120
#define INF 99999999999.0
int n, m;
struct node
{
double x, y;
}a[maxn];
inline double get_dis(node a, node b)
{
double x = a.x - b.x;
double y = a.y - b.y;
return sqrt(x*x + y*y);
}
double map[maxn][maxn];
bool flag[maxn];
int pre[maxn];
void dfs(int x)
{
flag[x] = true;
for (int i = 1; i <= n; i++)if (!flag[i] && map[x][i] != INF)
dfs(i);
}
bool check()
{
memset(flag, 0, sizeof(flag));
dfs(1);
for (int i = 1; i <= n; i++)
if (!flag[i])return false;
return true;
}
double solve()
{
memset(flag, 0, sizeof(flag));//flag是true的點是被去掉的點
int i, j, k;
double ans = 0;
while (1)
{
for (i = 2; i <= n; i++)if (!flag[i])
{
pre[i] = i;
map[i][i] = INF;
for (j = 1; j <= n; j++)if (!flag[j])
{
if (map[pre[i]][i]>map[j][i])
pre[i] = j;
}
}
for (i = 2; i <= n; i++)if (!flag[i])
{
bool mark[maxn];
memset(mark, 0, sizeof(mark));
for (j = i; j != 1 && !mark[j]; mark[j] = true, j = pre[j]);//尋找環,返回在環內的一點(注意從i出發能找到換不代表n在環內)
if (j == 1)continue;
i = j;
ans += map[pre[i]][i];
for (j = pre[i]; j != i; j = pre[j])
{
ans += map[pre[j]][j];
flag[j] = true;
}
for (j = 1; j <= n; j++)if (!flag[j] && map[j][i] != INF)
map[j][i] -= map[pre[i]][i];
for (j = pre[i]; j != i; j = pre[j])
{
for (k = 1; k <= n; k++)if (!flag[k] && map[j][k] != INF)
map[i][k] = min(map[i][k], map[j][k]);
for (k = 1; k <= n; k++)if (!flag[k] && map[k][j] != INF)
map[k][i] = min(map[k][i], map[k][j] - map[pre[j]][j]);
}
break;
}
if (i>n)//說明沒有環了。
{
for (j = 2; j <= n; j++)if (!flag[j])
ans += map[pre[j]][j];
return ans;
}
}
}
int main()
{
int i, j, x, y;
while (scanf("%d%d", &n, &m) != -1)
{
for (i = 1; i <= n; i++)
scanf("%lf%lf", &a[i].x, &a[i].y);
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
map[i][j] = INF;
for (i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
if (x == y)continue;//消除自環
map[x][y] = get_dis(a[x], a[y]);
}
if (!check())//檢查有向圖是否聯通
{
printf("poor snoopy\n");
}
else
{
printf("%.2lf\n", solve());
}
}
}