最小树形图,是指有向图的最小生成树。简单的来说,求一个图G0的最小树形图,先求出最短弧集合E0(从所以以vi为终点的弧中取一条最短的),若E0不存在(对于一给点vi没有入边),则图的最小树形图不存在。否则E0存在且不含有向环,则E0就是T0(最小树形图)中所有的边。如果存在且含有向环,则收缩成有向环为一点u,并形成图G1,继续求G1的最小树形图知道Gi,若Gi无最小树形图,则图G0也不存在最小树形图,存在最小树形图Ti就逐层展开得到T0.
最小树形图就是最小生成树在带权有向图上面的变种,也就是在有向图上找出一个边权和最小的有向树。这个问题需要给定一个带权的有向图以及有向树的根节点v。所谓有向树是指除了叶节点以外,所有节点都有指向其孩子节点的有向边。专业一点的说,“有向树(Directed Tree)是一个用于定义数据流或流程的逻辑结构。数据流的源点是根。数据流是单向分支离开根部到达目标,这个目标就是有向树的叶子。”。
求解最小树形图的第一个算法是1965年朱永津和刘振宏提出的复杂度为O(VE)的算法,该算法被称为朱-刘算法。在实现过程中,我们也可以表达为O(V3)的复杂度。
首先我们需要判断解是否存在。为了解决这个问题,我们只需要从指定的根节点v除法遍历一次就可以了。如果所有节点都可以被访问,那么解显然存在,否则不存在。另外,图中所有的自环也必须清除,因为这些自环显然不可能是最小树形图中的边。如果不知道什么是自环,请自行查阅图论相关定义。消除自环的目的是保证算法的时间复杂度。
下面是算法的流程:
- 对于图中除了根节点v以外的节点,选择一条权值最小的入边。所有被选择的边构成了一个子图,如果这个子图不含有有向环,那么这就是问题的解,算法结束;否则进行下一步。
- 我们需要收缩有向环,并且用一个新节点来代替。对于环中的点x,假定其选择边的权值为w0,y为环外的点,z为环收缩后新的节点。如果存在边<x,y>,那么<z,y>的边权值与<x,y>相同。如果存在边<y,x>且权值为w,那么新边<y,z>的权值就是w-w0。
- 重复执行1、2两个步骤。
要保证算法O(VE)的复杂度,要做到找最小入边O(E),找环O(V),收缩O(E)。再加上由于收缩环最多只进行V-1次(自环已经被消除),所以时间复杂度为O(VE)。在步骤2中,x的出边权值不变是因为收缩环并没有影响边所指向的节点,而入边的权值之所以要变小,是因为当新边被选择之后,x原来选择的入边就会被取消选择,所以实际花费的代价就是w-w0。
求所有边权和最小 ;
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 111
#define inf 1<<30
struct Edge{
int u,v;
double w;
}edge[MAXN*MAXN];
struct Node {
double x,y;
} node[MAXN];
int n,m;
double In[MAXN];
int pre[MAXN],visited[MAXN],ID[MAXN];
//root表示根结点,n是顶点树,m是边数
//最小树形图邻接表版本,三步走,找最小入弧,找有向环,缩环为点
double Directed_MST(int root,int n,int m)
{
int u,v,i,ansi;
double cnt=0;
while(true) {
//找最小入边
for(i=0; i<n; i++)In[i]=inf;
for(i=0; i<m; i++) {
u=edge[i].u;
v=edge[i].v;
if(edge[i].w<In[v]&&u!=v) {
pre[v]=u;//u->v;
if(u==root)//记录是root从哪一条边到有效点的(这个点就是实际的起点)
ansi=i;
In[v]=edge[i].w;
}
}
for(i=0; i<n; i++) {
if(i==root)continue;
if(In[i]==inf)return -1;//说明存在点没有入边
}
//找环
int cntcode=0;
memset(visited,-1,sizeof(visited));
memset(ID,-1,sizeof(ID));
In[root]=0;
//标记每一个环
for(i=0; i<n; i++) {
cnt+=In[i];
v=i;
while(visited[v]!=i&&ID[v]==-1&&v!=root) {
visited[v]=i;
v=pre[v];
}
//说明此时找到一个环
if(v!=root&&ID[v]==-1) {
//表示这是找到的第几个环,给找到的环的每个点标号
for(u=pre[v]; u!=v; u=pre[u]) {
ID[u]=cntcode;
}
ID[v]=cntcode++;
}
}
if(cntcode==0)break;//说明不存在环
for(i=0; i<n; i++) {
if(ID[i]==-1)
ID[i]=cntcode++;
}
//缩点,重新标记
for(i=0; i<m; i++) {
int v=edge[i].v;
edge[i].u=ID[edge[i].u];
edge[i].v=ID[edge[i].v];
//说明原先不在同一个环
if(edge[i].u!=edge[i].v) {
edge[i].w-=In[v];
}
}
n=cntcode;
root=ID[root];
}
return cnt;
}
double Get_dist(int i,int j)
{
double d1=(node[i].x-node[j].x)*(node[i].x-node[j].x);
double d2=(node[i].y-node[j].y)*(node[i].y-node[j].y);
return sqrt(d1+d2);
}
int main()
{
double ans;
while(~scanf("%d%d",&n,&m)){
for(int i=0;i<n;i++)scanf("%lf%lf",&node[i].x,&node[i].y);
for(int i=0;i<m;i++){
scanf("%d%d",&edge[i].u,&edge[i].v);
edge[i].u--;
edge[i].v--;
if(edge[i].u!=edge[i].v)edge[i].w=Get_dist(edge[i].u,edge[i].v);
else edge[i].w=inf;//消除自环
}
ans=Directed_MST(0,n,m);
if(ans==-1){
puts("poor snoopy");
}else
printf("%.2f\n",ans);
}
return 0;
}