符合最短路的最小生成树(总觉得名字起得很别扭)

提交链接

题目描述:

你知道黑暗城堡有 N 个房间,M 条可以制造的双向通道,以及每条通道的长度。
城堡是树形的并且满足下面的条件:
设 Di为如果所有的通道都被修建,第 i 号房间与第 1 号房间的最短路径长度;
而 Si为实际修建的树形城堡中第 i号房间与第 1 号房间的路径长度;
要求对于所有整数 i (1≤i≤N),有 Si=Di 成立。
你想知道有多少种不同的城堡修建方案。
当然,你只需要输出答案对 2^31−1 取模之后的结果就行了。

Input

第一行为两个由空格隔开的整数 N,M;
第二行到第 M+1 行为 3 个由空格隔开的整数 x,y,l:表示 x 号房间与 y 号房间之间的通道长度为 l。

Output

一个整数:不同的城堡修建方案数对 2^31−1取模之后的结果。

Sample Input

4 6
1 2 1
1 3 2
1 4 3
2 3 1
2 4 2
3 4 1
Sample Output

6

【样例说明】

扫描二维码关注公众号,回复: 12938262 查看本文章

一共有 4 个房间,6 条道路,其中 1 号和 2 号,1 号和 3 号,1 号和 4 号,2 号和 3 号,2 号和 4 号,3 号和 4 号房间之间的通道长度分别为 1,2,3,1,2,1。

而不同的城堡修建方案数对 2^31−1取模之后的结果为 6。

【数据范围】
对于全部数据,1≤N≤1000 ,1≤M≤N(N−1)/2 ,1≤l≤200。

题意分析:
连题意都没看懂,请教了大佬之后才对题意有了一点点理解,唉我觉得我真是太菜了,下面咱来说说题意

该题所求
有多少种不同的城堡修建方案,而且修建城堡必须满足是树形,最小生成树,但要满足最短路,有点迷糊。

树的特点:

  1. n个顶点,n-1条边,且无环
  2. 只利用n-1条,所有顶点都连通
  3. 任意两点之间只有唯一的简单路径
  4. 任意删除一条边后该图便不能连通

最小生成树的特点:

  1. 最小边原则:图中权值最小的边(如果唯一的话)一定在最小生成树上
  2. 唯一性定理:对于一个图,如果图中的边权值都不相同,则图的最小生成树是唯一的,反之不然。

Di表示的意义:
1号房间到另外其它房间的最短路
Si表示的意义:
只能修建n-1条边,因此就不能保证1到各个点的都是最短路,只能保证n-1条边的总和是最小的,但是因为有下面条件的存在,使得Si的意义发生了变话。
要求对于所有整数 i (1≤i≤N),有 Si=Di 成立,因为Si和Di都表示顶点i到1的距离,而现在他们必须相等了,因此,此刻所求是最小生成树,不仅仅要在边的总和上最小而且各点到1的距离也必须最小,因为根据树的唯一性定理,可以让我们联想到,之所以让求最小生成树的个数,那么题中给的图一定会有很多边权的值相同的边而且还会有很多的环。

明明该题让求的是最小生成树的个数,那为什么却用最短路算法写了出来

这是因为该生成树必须满足最最短路,所以无论如何都要求最短路,而且在求完最短路后,跑一下下面的代码又可以求出最小生成树的个数。

for(i=2;i<=n;i++)
{
	/*	
    该判断不用写也可以,当cnt是0时,也可说明。 
	if(dis[i]==INF)//存在一个点不能到达,那么一定不可能建成一棵最小生成树 
	{
		ans=0;
		break;
	}
	*/
	cnt=0;
	for(j=1;j<=n;j++)
	{
		if(e[j][i]!=INF&&i!=j)//j到i有路,并且i不能i,没意义
		{
			if(dis[i]==dis[j]+e[j][i])//找路,寻找顶点1到顶点i的最短路径有多少条 
				cnt++;
		}
	} 		
	ans=ans*cnt%MOD;//假如1到其它顶点的最短路径有多条,那么把最终形成最小生成树的最短路径数就是用连乘来算 
}

下面给一个简单图,希望能理解,其实我也似懂非懂
在这里插入图片描述

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MOD=(1<<31)-1;
const int INF=0x3f3f3f3f; 
int e[1010][1010];//邻接矩阵存图
int dis[1010];//用来存储1到i的最短路
int book[1010];//用来标记哪些顶点已找到最短路 
int n,m;
void Dijkstra()//求1到其它各点的最短路 
{
	int i,j,minn,u,v;
	//初始化dis[i] 
	for(i=1;i<=n;i++)
		dis[i]=e[1][i];
	
	//所有点都未被找到最短路 
	memset(book,0,sizeof(book));
	book[1]=1;//1首先找到最短路

	for(i=1;i<=n-1;i++)//一次循环便可以找到一个顶点到1的最短路
	{
		minn=INF;u=-1;//每次都寻找离源点最近的顶点 
		for(j=1;j<=n;j++)
		{
			if(!book[j]&&minn>dis[j])//顶点j还未找到最短路 
			{
				minn=dis[j];
				u=j;
			}
		}
		if(u==-1)//不能找到离源点最小的,因为可能存在某些顶点是1根本到达不了的,那自然不能寻到n-1次了	
			break;
		book[u]=1;//顶点u已是到1的最短距离了 
		
		//利用u顶点去缩短别的点到1的最短距离 
		for(v=1;v<=n;v++)
		{
			if(e[u][v]!=INF||!book[v])//说明u到v有路,看看能不能利用u顶点使得v顶点到1的最短距离变的更小,或者1到v的距离已经最小了,不可能再短了
				dis[v]=min(dis[v],dis[u]+e[u][v]); 
		}
	}
}
int main()
{	
	while(~scanf("%d %d",&n,&m))
	{
		int i,j;
		//初始化i,j的距离 
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
				if(i==j)	e[i][j]=0;
				else		e[i][j]=e[j][i]=INF;//C语言中规定不能连续赋值,不过很多时候都提交对了 
	
		while(m--)
		{
			int u,v,w;
			scanf("%d %d %d",&u,&v,&w);
			e[u][v]=e[v][u]=min(e[u][v],w);//防止出现重边,权值被覆盖 
		}
		Dijkstra();
	
		ll ans=1,cnt; //必须为ll,不然爆数据
		for(i=2;i<=n;i++)
		{
			/* //该判断不用写也可以,当cnt是0时,也可说明。 
			if(dis[i]==INF)//存在一个点不能到达,那么一定不可能建成一棵最小生成树 
			{
				ans=0;
				break; 
			}
			*/
			cnt=0;
			for(j=1;j<=n;j++)
			{
				if(e[j][i]!=INF&&i!=j)//j到i有路,并且i不能i,没意义
				{
					if(dis[i]==dis[j]+e[j][i])//找路,寻找顶点1到顶点i的最短路径有多少条 
						cnt++;
				}
			} 
			ans=ans*cnt%MOD;//假如1到其它顶点的最短路径有多条,那么把最终形成最小生成树的最短路径数就是用连乘来算 
		}
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Helinshan/article/details/110518799