题意
有向图中有 N 个点,M 条边。建立最小树状结构,使从 1 点出发能到达其余各点。
思路
有向图最小树状图(DSMT)裸题,朱刘算法。
朱刘算法
算法过程
1.对于每一个点找出它的权值最小的入边。同时判断从 根 点出发能否到达其余各点,不能则不能构成 DSMT。
2.判断图中是否存在环。如果不存在环,则将前一步中各点的最小入边权加和就是答案。如果有环,则需要将环缩成一个点,建立新图进行求解。
3.缩点。特别的,对于终点在环上的边,其权值需改变(见AC代码)。对新图进行求解。
提醒
1.朱刘算法的前提是已经屏蔽了原图中的自环。自环显然不会参与构成最小最小树形图。
2.除了边数组外,算法主要用到四个辅助数组 Pre, Id, Vis, In。
3.算法函数中的 res 只需要在开头初始化一次。每次建立新图后重新求解的答案直接累加到 res 上。
4.要注意判环之前的四个初始化操作。
5.注意缩点时点权值改变的公式。这里把不在环上的边全都改变了,因为不再环上的边的最小权已经加到 res 中了。不改环上的边是因为它们在新图中被变成了自环,不再参与运算了。
题目链接
http://poj.org/problem?id=3164
AC代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn = 100 + 10;
const int maxm = 1e4 + 10;
const double inf = 1e9;
int X[maxn], Y[maxn];
double dist(int a, int b)
{
if(a == b) return inf;
return sqrt(1.0 * ((X[a] - X[b]) * (X[a] - X[b]) + (Y[a] - Y[b]) * (Y[a] - Y[b])));
}
/************************朱刘算法模板****************************/
int N, M;
int From[maxm], To[maxm]; //边的起点、终点、权值
double Cost[maxm];
int Pre[maxn], Id[maxn], Vis[maxn]; //DSMT中点的前驱,锁点后的新编号,找环时的标记数组
double In[maxn]; //每个点的最小入边权
double DSMT(int root) //root是根,出发点
{
double res = 0.0; //定义在最开头整个算法只需要初始化这一次
while(1)
{
for(int i= 1; i<= N; i++) In[i] = inf; //求最小入边权,同时求出前驱
for(int i= 1; i<= M; i++) //这里要注意排除自环
{
int u = From[i], v = To[i];
if(In[v] > Cost[i] && u != v)
{
In[v] = Cost[i];
Pre[v] = u;
}
}
for(int i= 1; i<= N; i++) //从root出发,有的点不能到达,不能构造DSMT
if(i != root && In[i] == inf) return -1;
In[root] = 0; //根的最小入边权置为0
int node = 0; //缩点后,新的点数
memset(Id, -1, sizeof Id); //初始化缩点后,每个点新的标号
memset(Vis, -1, sizeof Vis); //初始化访问标记数组
for(int i= 1; i<= N; i++)
{
res += In[i]; //累加各点的最小入边权
int v = i; //沿着前驱找,判断是否有环
while(Vis[v] != i && Id[v] == -1 && v != root) //Vis[v] == i表示这个点在这次查找中已经出现过一次,找到环
{ //Id[v] != -1表示这个点已经在环上了
Vis[v] = i;
v = Pre[v];
}
if(Id[v] == -1 && v != root) //满足这两个条件说明是找到环的情况
{ //给环上点一个统一的新标号
++ node;
for(int u = Pre[v]; u != v; u = Pre[u]) //仍然是沿着前驱改下去
Id[u] = node;
Id[v] = node; //别忘了v点
}
}
if(node == 0) break; //新点数为0,表示没有出现环,res就是答案
/*缩点,重建新图过程*/
for(int i= 1; i<= N; i++) //首先给不在环上的点赋一个新标号
if(Id[i] == -1) Id[i] = ++ node;
for(int i= 1; i<= M; i++) //将边和新标号对应起来
{
int v = To[i]; //保留边中原始的终点,因为它对应着v的入边权
From[i] = Id[From[i]]; //更新边信息
To[i] = Id[To[i]];
if(From[i] != To[i]) Cost[i] -= In[v]; //环以外的边需要进行处理
}
N = node; //更新点的数目
root = Id[root]; //更新根的标号
}
return res;
}
/************************朱刘算法模板****************************/
int main()
{
while(scanf("%d %d", &N, &M) != EOF)
{
for(int i= 1; i<= N; i++)
scanf("%d %d", X + i, Y + i);
for(int i= 1; i<= M; i++)
{
scanf("%d %d", &From[i], &To[i]);
Cost[i] = dist(From[i], To[i]);
}
double ans = DSMT(1);
if(ans == -1) printf("poor snoopy\n");
else printf("%.2f\n", ans);
}
return 0;
}