POJ3164 确定根的最小树形图

本题题意:给你一堆点的坐标n个坐标,然后m对点表示a点到b点有单向边。然后指定第一个点,问第一个点能不能通过已经给出的边连接到所有点。如果不能输出可怜的某狗狗的名字,如果可以输出最短的路径和。

思路:在生成树里看到的这个专题,想了一下因为要单点连接到所有点,不是最小生成树也不是次小生成树。百度了一下题解,又发现了最小树形图这个神奇的东西。然后学到了朱刘算法。算法我就不说了(怕说不清坑人),思路就直接给出大佬的博客最小树形图-朱刘算法。下面附了本题代码(因为大佬博客里的例题不是这个),代码注释应该能帮助刚学这个算法的人理解。。。
题目链接

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<map>
#include<queue>
#include<cmath>
#include<set>
#include<list>
using namespace std;
typedef long long ll; 
typedef double db;
const int inf=0x3f3f3f;
const int maxn=210,maxm=50010;
int pre[maxn],vis[maxn],idd[maxn],n,m,pos,sz,x[maxn],y[maxn];
db inn[maxn];
struct node{
	int u,v;
	db l;
}ed[maxm];
db dit(int a,int b)
{
	return sqrt((x[a]-x[b])*(x[a]-x[b])*1.0+(y[a]-y[b])*(y[a]-y[b]));
}
void add(int a,int b)
{
	sz++;
	ed[sz].u=a;
	ed[sz].v=b;
	ed[sz].l=dit(a,b);
}
db zhuliu(int rt)
{
	db ans=0;
	while(true)
	{
		for(int i=1;i<=n;i++)inn[i]=inf;//初始化 
		for(int i=1;i<=sz;i++)//开始找最小入边 
		{
			if(ed[i].u==ed[i].v)continue;
			else if(ed[i].l<inn[ed[i].v])
			{
				pre[ed[i].v]=ed[i].u;
				inn[ed[i].v]=ed[i].l;
			}
		}
		for(int i=1;i<=n;i++)//如果有点没有最小入边,证明无法建树返回。 
		{
			if(i!=rt && inn[i]==inf)return -1;
		}
		int cnt=0;
		memset(idd,0,sizeof(idd));
		memset(vis,0,sizeof(vis));
		inn[rt]=0;
		for(int i=1;i<=n;i++)//找环并标号 
		{
			ans+=inn[i];
			int x=i;
			while(!idd[x] && vis[x]!=i && x!=rt)//找环并标记  1.前驱结点在环上 2. 前驱结点已经在本轮搜索中被搜到 ,证明已经成环 
			{                                     // 3.前驱结点是根节点,不能成环 。 
				vis[x]=i;  //给本轮搜索标号,要是搜到同号证明可成环 
				x=pre[x];
			}
			if(x!=rt && !idd[x]) //如果前驱结点不是根结点  并且前驱结点不在环上 
			{
				idd[x]= ++cnt;
				for(int j=pre[x];j!=x;j=pre[j])idd[j]=cnt;	
			}
		}
		if(!cnt)break;//如果没有环证明已经生成最小树形图,退出循环 
		for(int i=1;i<=n;i++)
		{
			if(!idd[i])idd[i] = ++cnt;//给单独的没有在环上的点编号。 
		}
		for(int i=1;i<=m;i++)//进行缩点操作 
		{
			int x=ed[i].u,y=ed[i].v;
			ed[i].u=idd[x];//给每条边的两个结点更新为缩点后点的编号 
			ed[i].v=idd[y];
			if(idd[x]!=idd[y])ed[i].l-=inn[y];
		}
		n=cnt;//更新缩点后的结点数量 
		rt=idd[rt];//更新缩点后根结点的新编号 
	}
	return ans;
}
int main()
{
	while(cin>>n>>m)
	{
		sz=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&x[i],&y[i]);
		}
		for(int i=1;i<=m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			add(a,b);
		}
		db ans=zhuliu(1);
		if(ans==-1)puts("poor snoopy");
		else printf("%.2f\n",ans);//只能用f不能双精度输出,原因不详 
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值