最小树形图的朱刘算法好像是很久之前学的啦
不过早忘光了,今天正好来复习一下。
突然发现思路很清晰嘛(果然是因为找到了一个很好的板子吗)
首先给每个点找最小的入边,如果一个点没有入边,那么可以肯定无解了。(假设有入边但是从根节点到不了,那么必然会在后面缩点,然后就没有入边了)
这时可以将每条入边暂时加入到解中。
然后对于每个点来说找它所有的一个环,然后把环缩成一个点,也就是给环上的每个点打个标号。
如果无环,退出。
将每条边的点用标号重新标记,如果该条边连接两个环,则将边权减去终点的最小入边(假设之后选择这条边,则此时的-in[v]与之前的+in[v]抵消)
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int N=100+5; const int M=10000+5; const double inf=1000000000.0; struct Edge{ int u,v; double w; }e[M]; double in[N]; int pre[N],id[N],vis[N]; double Zhu_Liu(int root,int n,int m){ double ans=0; while(true){ //step 1 : minimum in edge for(int i=1;i<=n;i++)in[i]=inf; for(int i=1;i<=m;i++){ int u=e[i].u,v=e[i].v; if(u!=v&&in[v]>e[i].w) in[v]=e[i].w,pre[v]=u; } for(int i=1;i<=n;i++) if(i!=root&&in[i]==inf)return -inf; //step 2 : find circles int nv=0; memset(id,-1,sizeof(id)); memset(vis,-1,sizeof(vis)); in[root]=0; for(int i=1;i<=n;i++){ ans+=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){ id[v]=++nv; for(int u=pre[v];u!=v;u=pre[u]) id[u]=nv; } } if(!nv)break; for(int i=1;i<=n;i++) if(id[i]==-1)id[i]=++nv; //step 3 : shrink node and re-marking for(int i=1;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=nv; root=id[root]; } return ans; } double x[105],y[105]; double sqr(double x){return x*x;} double dist(int i,int j){ return sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j])); } int main(){ //freopen("a.in","r",stdin); int n,m; while(scanf("%d%d",&n,&m)==2){ for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]); for(int i=1;i<=m;i++){ scanf("%d%d",&e[i].u,&e[i].v); if(e[i].u==e[i].v)i--,m--; else e[i].w=dist(e[i].u,e[i].v); } double ans=Zhu_Liu(1,n,m); if(ans<0)puts("poor snoopy"); else printf("%.2f\n",ans); } return 0; }