无向图最小环问题

CODEVS 2611 观光旅游
题目描述 Description

某旅游区里面有N个景点。两个景点之间可能直接有道路相连,用a[i][j]表示它的长度,否则它们之间没有直接的道路相连。这里所说的道路是没有规定方向的,也就是说,如果从i到j有直接的道路,那么从j到i也有,并且长度与之相等。

旅游区规定:每个游客的旅游线路只能是一个回路(好霸道的规定)。也就是说,游客可以任取一个景点出发,依次经过若干个景点,最终回到起点。一天,Smart决定到这个景区来旅游,由于他实在已经很累了,于是他决定尽量少走一些路。

他想请你帮他求出最优的路线。怎么样,不是很难吧?

输入描述 Input Description

输入有多组数据。对于每组数据:

第一行有两个正整数N,M,分别表示景点个数和有多少对景点之间直接有边相连(N≤100,M≤10000);

接下来M行,每行三个正整数,分别表示一条道路的两端的编号,以及这条道路的长度(长度≤1000)。

输出描述 Output Description

对于每组数据,输出一行,如果该回路存在,则输出一个正整数,表示该回路的总长度;否则输出“No solution.”(不要输出引号)


嗯 一个经典的无向图最小环问题
然鹅今天之前我完全没听过这东西

总之呢就是边做floyd边更新最小值

引用一段 Robot_Asia dalao的讲解 (见原文

抛开Dijkstra算法,进而我们想到用Floyd算法。我们知道,Floyd算法在进行时会不断更新矩阵dist(k)。设dist[k,i,j]表示从结点i到结点j且满足所有中间结点,它们均属于集合{1,2,⋯ ,k}的一条最短路径的权。其中dist[0,i,j ]即为初始状态i到j的直接距离。对于一个给定的赋权有向图, 求出其中权值和最小的一个环。我们可以将任意一个环化成如下形式:u->k->v ->(x1-> x2-> ⋯ xm1)-> u(u与k、k与v都是直接相连的),其中v ->(x1-> 2-> ⋯ m)-> u是指v到u不经过k的一种路径。
在u,k,v确定的情况下,要使环权值最小, 则要求 (x1一>x2->⋯一>xm)->u路径权值最小.即要求其为v到u不经过k的最短路径,则这个经过u,k,v的环的最短路径就是:[v到u不包含k的最短距离]+dist[O,u,k]+dist[O,k,v]。我们用Floyd只能求出任意2点间满足中间结点均属于集合{1,2,⋯ ,k}的最短路径,可是我们如何求出v到u不包含k的最短距离呢?
现在我们给k加一个限制条件:k为当前环中的序号最大的节点(简称最大点)。因为k是最大点,所以当前环中没有任何一个点≥k,即所有点都<k。因为v->(x1->x2->…xm)->u属于当前环,所以x1,x2,⋯ ,xm<k,即x1,x2.⋯。xm≤k一1。这样,v到u的最短距离就可以表示成dist[k一1 ,u,v]。dist[k一1,v,u]表示的是从v到u且满足所有中间结点均属于集合{1,2,⋯ ,k一1}的一条最短路径的权。接下来,我们就可以求出v到u不包含k的最短距离了。这里只是要求不包含k,而上述方法用的是dist[k一1,v,u],求出的路径永远不会包含k+l,k+2,⋯ 。万一所求的最小环中包含k+1,k+2,⋯ 怎么办呢?的确,如果最小环中包含比k大的节点,在当前u,k,v所求出的环显然不是那个最小环。然而我们知道,这个最小环中必定有一个最大点kO,也就是说,虽然当前k没有求出我们所需要的最小环,但是当我们从k做到kO的时候,这个环上的所有点都小于kO了.也就是说在k=kO时一定能求出这个最小环。我们用一个实例来说明:假设最小环为1—3—4—5—6—2—1。的确,在u=l,v=4,k=3时,k<6,dist[3,4,1]的确求出的不是4—5—6—2—1这个环,但是,当u=4,v=6,k=5或u=5,v=2,k=6时,dist[k,v,u]表示的都是这条最短路径.所以我们在Floyd以后,只要枚举u.v,k三个变量即可求出最小环。时间复杂度为O(n3)。我们可以发现,Floyd和最后枚举u,v,k三个变量求最小环的过程都是u,v,k三个变量,所以我们可以将其合并。这样,我们在k变量变化的同时,也就是进行Floyd算法的同时,寻找最大点为k的最小环。

嗯 讲得很透彻 上课没听懂 ε=ε=ε=┏(゜ロ゜;)┛

模板照着老师说的写的
dis[i][j]表示ij的最短距离
g[i][j]是初始距离

for(int k=1;k<=n;++k)
{
	for(int i=1;i<k;++i)
	{
		for(int j=i+1;j<k;++j)
		{
			ans=min(ans,dis[i][j]+g[i][k]+g[k][j]);
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
		}
	}
}

其实不太好理解的点有几个

  • Q:为什么先枚举k
    A:我们是把k当作中转站看能不能更新ij的距离
    这样枚举k自然方便一些
  • Q:为什么先更新ans再更新dis(先更新答案后floyd)
    A:就像上面dalao所说,我们更新ans时需要的是dis[k-1,i,j]而不是dis[k,i,j],开三维数组自然没有这个问题,不过既然本着能省则省的思想开了二维,就要后更新dis
  • Q:为什么i j<k
    A:我们只求好了1~k-1之间(k点加入之前)的最优路径,k~n会在后面得到更新,且肯定比现在更新得到答案更优

然后,关于floyd有种神奇的理解
k点当作中转站相当于向图中加入k点,同时用它更新现有图上最短路(好像有点像dp??)
这也解释了为什么先枚举k,点总要一个个加嘛

最后附代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int N=105;
long long n,m;
long long dis[N][N],g[N][N];
int main(){
	while(scanf("%lld %lld",&n,&m)==2)
	{
		long long u,v,w;
		long long ans=INF;
		for(int i=1;i<=n;++i)
		{
			for(int j=i+1;j<=n;++j)
			{
				if(i==j)
					dis[i][j]=dis[j][i]=g[i][j]=g[j][i]=0;
				else
					dis[i][j]=dis[j][i]=g[i][j]=g[j][i]=INF;
			}
		}
		/*memset(dis,INF,sizeof(dis));
		memset(g,INF,sizeof(g));
		for(int i=1;i<=n;++i)
		{
			dis[i][i]=g[i][i]=0;
		}*/
		//这样初始化是不对的 数组元素值不等于INF
		for(int i=0;i<m;++i)
		{
			cin>>u>>v>>w;
			dis[u][v]=g[u][v]=dis[v][u]=g[v][u]=min(g[u][v],w);
		}
		for(int k=1;k<=n;++k)
		{
			for(int i=1;i<k;++i)
			{
				for(int j=i+1;j<k;++j)
				{
					ans=min(ans,dis[i][j]+g[i][k]+g[k][j]);
				}
			}
			for(int i=1;i<=n;++i)
			{
				for(int j=1;j<=n;++j)
				{
					dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
				}
			}
		}	
		if(ans==INF)
			printf("No solution.\n");
		else
			printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值