POJ 3164 Command Network

最小树形图的朱刘算法好像是很久之前学的啦

不过早忘光了,今天正好来复习一下。

突然发现思路很清晰嘛(果然是因为找到了一个很好的板子吗)

首先给每个点找最小的入边,如果一个点没有入边,那么可以肯定无解了。(假设有入边但是从根节点到不了,那么必然会在后面缩点,然后就没有入边了)

这时可以将每条入边暂时加入到解中。

然后对于每个点来说找它所有的一个环,然后把环缩成一个点,也就是给环上的每个点打个标号。

如果无环,退出。

将每条边的点用标号重新标记,如果该条边连接两个环,则将边权减去终点的最小入边(假设之后选择这条边,则此时的-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;
}


你可能感兴趣的:(POJ 3164 Command Network)