传送门
最小树形图定义:
在给定的有向带权图中指定一个点为root,求一棵以root为根的有向生成树T使得T中边权之和最小。解决此类问题的第一个算法是朱刘算法,时间复杂度O(VE)。
算法流程:
1.找到除了指定的root以外所有点的最小入边以及对应的前驱,分别记为in[i]/pre[i]
2.如果操作1执行完后仍有in[i]等于初值INF,说明图不连通,不存在最小树形图
3.找出图中所有环,重新编号
4.对于每个环,更新环外的点到环上的点的距离
5.重复操作3和4直到不再有环存在
模板题。。。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAXN=105,MAXM=1e4+5;
const double INF=0x3f3f3f3f;
int n,m;
int pre[MAXN],id[MAXN],col[MAXN];
double in[MAXN];
struct EDGE {
int u,v;
double w;
}e[MAXM];
struct Node {
double x,y;
}nd[MAXN];
inline double dis(Node a,Node b) {
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double dmst(int root) {
double ret=0;
while (1) {
for (int i=1;i<=n;++i) in[i]=INF;
for (int i=0;i<m;++i) {
int u=e[i].u,v=e[i].v;
if (u^v&&e[i].w<in[v])
in[v]=e[i].w,pre[v]=u;
}
for (int i=1;i<=n;++i)
if (i^root&&in[i]==INF) return -1;
int tn=0;
memset(id,-1,sizeof(id));
memset(col,-1,sizeof(col));
in[root]=0;
for (int i=1;i<=n;++i) {
ret+=in[i];
int v=i;
while (col[v]!=i&&id[v]==-1&&v^root)
col[v]=i,v=pre[v];
if (v^root&&id[v]==-1) {
id[v]=++tn;
for (int p=pre[v];p^v;p=pre[p])
id[p]=tn;
}
}
if (!tn) break;
for (int i=1;i<=n;++i)
if (id[i]==-1) id[i]=++tn;
for (int i=0;i<m;++i) {
int 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].w-=in[v];
}
n=tn;
root=id[root];
}
return ret;
}
int main() {
// freopen("poj 3164.in","r",stdin);
while (~scanf("%d%d",&n,&m)) {
for (int i=1;i<=n;++i) scanf("%lf%lf",&nd[i].x,&nd[i].y);
for (int i=0;i<m;++i) {
int a,b;
scanf("%d%d",&a,&b);
e[i].u=a,e[i].v=b;
if (a^b) e[i].w=dis(nd[a],nd[b]);
}
double ans=dmst(1);
if (ans!=-1) printf("%.2f\n",ans);
else puts("poor snoopy");
}
return 0;
}