观光之旅(算法竞赛进阶指南)

给定一张无向图,求图中一个至少包含 3 个点的环,环上的节点不重复,并且环上的边的长度之和最小。

该问题称为无向图的最小环问题。

你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。

输入格式

第一行包含两个整数 N 和 M,表示无向图有 N 个点,M 条边。

接下来 M 行,每行包含三个整数 u,v,l,表示点 u 和点 v 之间有一条边,边长为 l。

输出格式

输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出 No solution.。

数据范围

1≤N≤100,

1≤M≤10000,

1≤l<500

输入样例:

5 7

1 4 1

1 3 300

3 1 10

1 2 16

2 3 100

2 5 15

5 3 20

输出样例:

1 3 5 2

摘自AcWing 344. 观光之旅 - AcWing

性质

设环的形式是:i<->k<->j , i<–>j (i,j,k不同)

 

1.在i~j的最短道路中,一定没有环

有环的话走环相当于回到原点,白占了边权。这里是没有负权边,所以最短路肯定不含环。

2. 设i,j之间的最短道路经过点k(不同于i,j),则i~k , k~j之间必然没有交集

有交集的话会从交点那链接i,j会形成更短的路,矛盾

因此借用性质2来求解道路,每次把i <-> j 之间划分成 i<->k , k<->j,(先确定首尾,在逐渐填补中间的链接点),并确保最后输出顺序正确

算法采用Floyd

大致思路

起点,终点,中间点 ,已经构成了含三个点的环。所以

给定一张无向图,求图中一个至少包含 3 个点的环,

 这个条件不用管了。

要求长度最短的环,首先i <-> j相连的必须是最短的边,我们要用Floyd求最短路,同时我们要枚举

 i <-> j中的所有k点。

代码

#include <bits/stdc++.h>
using namespace std ;

const int N = 110 , M = 10010 , INF = 0x3f3f3f3f ;

typedef long long LL ;
int g[N][N] , d[N][N] ;
int pos[N][N] ; //记录当前状态由哪个点转移过来
vector<int> path;//存环
int n , m ;

//确保顺序正确,辅助get_path
void dfs(int i , int j )    //i->j之间的路,输出i到j之间不包括i和j的道路
{
	int k = pos[i][j] ;
	
	if( k == 0 )    return ;    //如果是0,说明i,j之间不经过除i,j之外的其他点
	
	dfs(i , k); //i->newk
	path.push_back(k);  //k
	dfs(k , j); //newk->j
	
}

void get_path(int i , int j , int k )
{
	path.clear() ;
	path.push_back(k);//k->i->j->k,先补一个k
	path.push_back(i);
	dfs(i , j) ;//去求i,j中被缩了的点    
	path.push_back(j);
}

int main()
{
	cin >> n >> m ; 
	
	memset(g , 0x3f ,sizeof g) ;
	for(int i = 0 ; i <= n ; i++ )  g[i][i] = 0 ;
	
	int a , b , c ;
	for(int i = 0 ; i < m ; i++ )
	{
		cin >> a >> b >> c ;
		g[a][b] = g[b][a] = min(g[a][b] , c) ;
	}
	
	memcpy(d , g , sizeof d );  //原图
	long long res = INF ;
	
	for(int k = 1 ; k <= n ; k++ )
	{
	
		
		//至少包含三个点的环所经过的点的最大编号是k
		for(int i = 1 ; i < k ; i++ )  //至少包含三个点,i,j,k不重合
			for(int j = i + 1 ; j < k ; j ++ )
				if(res > (LL)g[i][j] + d[i][k] + d[k][j] )
		{
			res = g[i][j] + d[i][k] + d[k][j] ;
			get_path(i , j , k) ;
		}
		//存下最短路和每个点的中间点
		for(int i = 1 ; i <= n ; i++ )
			for(int j = 1 ; j <= n ; j++ )
				if(g[i][j] > g[i][k] + g[k][j])
		{
			g[i][j] = g[i][k] + g[k][j] ;
			pos[i][j] = k ; 
		}
	}
	
	if(res == INF)  
		cout << "No solution." << endl;
	else
	{
		for(auto x : path)
			cout << x << ' ' ;
		cout << endl;
	}
	
	return 0;
}

注意事项

这里要先找点存环,不能先更新中间点。因为我们在选用这个k点成环时,k点肯定不能用来更新i,j路的最小值,不然这两个点就成一个点了。

 

posted @ 2022-09-24 21:16  zyc_xianyu  阅读(31)  评论(0编辑  收藏  举报