POJ3164 最小树形图 有向图的最小生成树 模板题 朱刘算法 朱永津-刘振宏算法

本文介绍了在战争背景下,snoopy为修复littleken指挥网络而设计的一道算法问题——构建最短总长度的有向通信网络。网络由节点组成,节点间可以建立直线连接进行单向通信。朱刘算法用于解决在限制条件下找到最小树形图的问题,以确保从节点1(littleken总部)可以到达所有其他节点。输入包含节点数、可建立连接的边数以及坐标信息,输出是最短总线长,不存在解则输出空字符串。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Command Network
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 12833 Accepted: 3717

Description

After a long lasting war on words, a war on arms finally breaks out between littleken’s and KnuthOcean’s kingdoms. A sudden and violent assault by KnuthOcean’s force has rendered a total failure of littleken’s command network. A provisional network must be built immediately. littleken orders snoopy to take charge of the project.

With the situation studied to every detail, snoopy believes that the most urgent point is to enable littenken’s commands to reach every disconnected node in the destroyed network and decides on a plan to build a unidirectional communication network. The nodes are distributed on a plane. If littleken’s commands are to be able to be delivered directly from a node A to another node B, a wire will have to be built along the straight line segment connecting the two nodes. Since it’s in wartime, not between all pairs of nodes can wires be built. snoopy wants the plan to require the shortest total length of wires so that the construction can be done very soon.

Input

The input contains several test cases. Each test case starts with a line containing two integer N (N ≤ 100), the number of nodes in the destroyed network, and M (M ≤ 104), the number of pairs of nodes between which a wire can be built. The next N lines each contain an ordered pair xi and yi, giving the Cartesian coordinates of the nodes. Then follow M lines each containing two integers i and j between 1 and N (inclusive) meaning a wire can be built between nodei and node j for unidirectional command delivery from the former to the latter. littleken’s headquarter is always located at node 1. Process to end of file.

Output

For each test case, output exactly one line containing the shortest total length of wires to two digits past the decimal point. In the cases that such a network does not exist, just output ‘poor snoopy’.

Sample Input

4 6
0 6
4 6
0 0
7 20
1 2
1 3
2 3
3 4
3 1
3 2
4 3
0 0
1 0
0 1
1 2
1 3
4 1
2 3

Sample Output

31.19
poor snoopy

Source


题意:多组数据,给点数n,边数m,下面n行点坐标,m行边的出点入点,求1为根的最小树形图边权值。
算法:朱永津-刘振宏算法。
算法思想:
0. 若从根开始走有哪个点无法走到则没有最小树形图,甚至树形图都没有。
1. 对每个点求一个边权最小的前驱(求最小弧),并且对这些最小弧建新图(思想上建图)
2. 在新图上对每个环缩点,然后循环过程【1】;
3. 缩到不能再缩时(某次遍历没有环可以缩),则ans=∑除根节点以外每个点的最小弧权值
4. 返回ans算法完成实现。
ps .  1:算法的实现当然是需要一定的技巧的~~!
ps .  2:下附构造流程图和代码,实现过程在代码中有详细解释与举例。
#include<cstdio>
#include<cstring>
#include<cmath>
#define inf 2000000000
/*inf可以更大一点*/
#define N 200
#define M 2000
#define NN 50000
using namespace std;
struct Fiona
{
	double x,y;
}s[N];/*点*/
struct Syndra
{
	int u,v;
	double w;
}e[NN];/*边*/
int pre[N],f[N],visit[N];/*前驱,缩点时属于的点,缩点时的一个环判断*/
double l[N];/*l[i],i的最小弧长度*/
double dist(Fiona A,Fiona B)
{
	return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
	/*求坐标之间直线距离*/
}
double Directed_MST(int root,int n,int m)
{
	int i,j,k;
	int u,v,cnt;
	double ans=0;
	while(1)
	{
		cnt=0;
		memset(f,0,sizeof(f));
		memset(visit,0,sizeof(visit));
/*1.求每个点的最小入边(求最小弧集)*/
		for(i=1;i<=n;i++)l[i]=inf;/*清最大值(这里清的2*10^9其实不是最大值,但是也够了)*/
		for(i=1;i<=m;i++)
		{
			u=e[i].u;v=e[i].v;
			if(l[v]>e[i].w&&u!=v)pre[v]=u,l[v]=e[i].w;/*更新最小弧*/
		}
		/*若某个非起点的点没有前驱(最小弧),
			即这个点无法通过别的某个点到达,
			则图非联通,则返回一个否定值↓↓↓*/
		for(i=1;i<=n;i++)if(l[i]==inf&&i!=root)return -1;
		l[root]=0;/*因为下面的循环要加上每个点的最小弧长度,而源点不要要入边,所以清一下*/
		for(i=1;i<=n;i++)
		{
			ans+=l[i];
			v=i;
			while(visit[v]!=i&&!f[v]&&v!=root)
			{/*从当前点往前驱找,直到找到一个环(visit[v]!=i)
				或者找到了源点,即无法接着找前驱了
				或者找到了某个已经被标记完的环(!f[v])*/
				visit[v]=i;/*不能在此处写visit[v]=1,然后判断!visit[v],
								因为在找的时候visit的作用是一个环回来,
								判断找到了一个环,而非是遍历过了*/
				/*如      ① 
				          ↓ 
					⑦ ←⑥ ←② ←⑤ 
				    ↑     ↓  ↑ 
			 		⑧ →⑨  ③ →④  
			 		除了①到②长度100000,其他都小于10可以吧?
					 然后手调一下就“理解深刻”了。
					 ps:如果整个图是一个个三角环组成点连成的链,
					 	那么是不是可以比较有效地卡一下这个版本的朱刘算法实现呢? 
				*/
				v=pre[v];/*向前遍历寻找*/
			}
			if(v!=root&&!f[v])
			{/*注意此处的“!f[v]”没有虽然不会错,
				但是这会导致对环内每个点重复标记,
				一方面略有效卡评测,一方面调试时看缩点会很恶心*/
				f[v]=++cnt;
				for(u=pre[v];u!=v;u=pre[u])f[u]=cnt;
				/*这里随意写了,只要保证该在一个环的在一个环就行*/
			}
		}
		if(!cnt)break;/*此处判断有多少环,即进行了几次缩点,若无法再缩(cnt==0)则跳出*/
		for(i=1;i<=n;i++)if(!f[i])f[i]=++cnt;/*把剩余的点标记*/
		for(i=1;i<=m;i++)
		{
			u=e[i].u;e[i].u=f[u];
			v=e[i].v;e[i].v=f[v];
			if(f[u]!=f[v])e[i].w-=l[v];
			/*O(m)对每条边进行权值缩减处理*/
			/*为什么要缩减呢? 
				  如:    ① 
				          ↓ 
					⑦ ←⑥ ←② ←⑤ 
				    ↑     ↓  ↑ 
			 		⑧ →⑨  ③ →④  
			 		除了①到②长度100000,其他都小于10。
		 		我们来手模拟一遍 : 
				 当第一次缩点完成,即2345被缩好后, 
				 我们的ans实际代表该图中除了① → ②以外所有边的权值和
				 而真正的答案应该是除了⑤ → ②以外所有边的权值和。
				 这样我们把②的所有入边权值减去⑤ → ②的长度,
				 使得下次ans+边权的时候加的是(w【① → ②】-w【⑤ → ②】)
				 即使得初始加的“⑤ → ②”边权值被删掉。(略有点网络流反向弧的感觉) 
 			*/
		}
		n=cnt;/*因为缩点了,所以我们需要修改一个点的总数*/
		root=f[root];
	}
	return ans;
}
int main()
{
	freopen("test.in","r",stdin);
	int i,n,m;
	double ans;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		/*此处的加点加边我不赘述了*/
		for(i=1;i<=n;i++)scanf("%lf%lf",&s[i].x,&s[i].y);
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&e[i].u,&e[i].v);
			if(e[i].u!=e[i].v)e[i].w=dist(s[e[i].u],s[e[i].v]);
			else i--,m--;//除去自边  
		}
		ans=Directed_MST(1,n,m);
		if(ans==-1)printf("poor snoopy\n");
		else printf("%.2f\n",ans);/*poj double 要用%f */
	}
	return 0;
}


 
 
 
 /*******************************************************************
 
再附个测试数据:
Input:
*******************************************************************
4 7
0 30
0 5
0 13
0 5
1 3
3 1
2 1
2 4
4 2
3 4
2 3
5 9
0 0
0 15
0 19
0 19
0 22
1 2
1 3
1 4
2 3
2 4
3 2
3 5
4 1
5 2
5 8
0 1
0 27
0 0
0 40
0 30
1 3
2 4
3 1
3 4
3 5
4 1
4 5
5 2
5 12
0 2
0 24
0 2
0 34
0 17
2 4
4 4
1 3
2 4
4 4
3 5
1 4
3 4
4 2
5 5
2 2
1 2
4 12
0 0
0 3
0 4
0 6
1 2
1 3
1 4
2 1
2 3
2 4
3 1
3 2
3 4
4 1
4 2
4 3
*******************************************************************
Output:
*******************************************************************
25.00
26.00
47.00
47.00
6.00
*******************************************************************/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值