观光之旅(算法竞赛进阶指南)
给定一张无向图,求图中一个至少包含 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
性质
设环的形式是: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路的最小值,不然这两个点就成一个点了。