算法
(Floyd) $O(n^3)$
设环的形式是:i<->k<->j , i<-->j (i,j,k不同)
floyd是典型的插点算法,每次插入点k,为此,在点k被[插入前]可计算i-j-k这个环
即此时中间节点为:1~k-1,即我们已经算出了任意i<->j的最短道路,中间经过的节点可以为 (1,2,3,...,k-1)
我们只需枚举所有以k为环中最大节点的环即可。
pos[i][j]:i~j的最短路中经过的点是k(即由这个状态转移过来),且这个k是此路径中编号最大的点(除i,j)//根据Floyd算法实质决定
这条道路存在以下两条性质
1.在i~j的最短道路中,一定没有环(显然)
2.设i,j之间的最短道路经过点k(不同于i,j),则i~k , k~j之间必然没有交集
2证:
如果有交集,设交点为k'(k' < k,根据Floyd算法实质相关),则存在道路:
i<->k'<->j , 由于[i<->k'] < [i<->k] , [j->k'] < [j->k]
显然这条道路更小,和假设矛盾所以一定没有交集
对于pos[i][j],如果pos[i][j] == 0 : 说明i~j的最短路没有经过其他节点
因此借用性质2来求解道路,注意书写顺序,确保最后输出顺序正确
每次把i <-> j 之间划分成 i<->k , k<->j
C++ 代码
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
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 ;
//确保顺序正确
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); //边界
path.push_back(i);
dfs(i , j) ; //k->i->j->k
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;
}
有个问题没想清楚,为什么循环的时候i和j都需要小于k啊,Floyd不是只需要经过的点小于k就可以了吗
啊?点的编号小于k啊,所以都要小于k。
只是为了避免经过相同的点,比如i == k时,三个点就变成两个点了。其实循环到n也是可以的,不过当i, j, k中有两个相同时就要continue一下
i和j都要小于k是因为我们规定了当前状态中:k是环中的最大节点
就是让三个点不是同一个号码,最简单的办法就是,类似于 5,6,7这样的,否则5,5,5就不符合条件,5,4,3还可能与3,4,5重复。
是怎么判定节点个数大于三的环
能保证是大于等于3,i,j,k三个,然后[i,j]存在大于等于0个
记录美好生活
这个题解太优雅了,给题主点赞👍
大佬,为什么递归,存k的操作,为什么智能放在中间,换一下顺序就错了
k是采用类似floyd的插点算法加上去来获取(i,j)间最短距离的,不放(i,j)中间还能放哪里呢?放i前面?
为什么要Longlong,最大的dist不应该是100 * 500 = 50000吗,为什么需要开longlong
两点之间没有直接通路的边长度是正无穷
明白了,感谢
想再问一下,为什么必须long long 进行强制转换,而不能使用#define int long long 或者直接把所有变量都定义成long long类型呢,我试了试,这样是错的
你memset赋值的时候可能出了问题 , 人工for循环赋值试一下
https://www.acwing.com/problem/content/submission/code_detail/5823607/ 看我的ac代码
后来解决了,不是赋值的问题,是因为当全部定义为long long 的时候,memset完,三个long long相加会爆long long,所以必须用int ,三个int 会爆int 但 不会爆long long,所以这里使用#define int long long 会出错
哦哦,刚刚理解错你意思了,确实是memset赋值的问题,for循环赋值就不会出现爆long long的问题了
嗯嗯
tql
任何的最短路都有你说的性质 “i ~ j”的最短路一定不存在环, 因为正权图, 最短路径肯定没环啊, 写的有点误导性
i~j的道路一定没有环(错误的吧, floyd啥性质 没有环?)
get_path为什么要先push k和i啊,大佬们
我的get_path是这样写的:
解释:因为要求i[HTML_REMOVED]j之间的环,而这个环是通过插入点k形成的,也就是
环的长度=$g[i][j]+d[i][k]+d[k][j]$
floyd为什么不能直接用一个d[][]存距离而是用g + d呢?
因为g数组是直接存的两点点之间的距离, 并不一定是最小, 而d数组存的是最小距离
因为本题需要两个距离:
两点间原始距离 g
两点间最短距离 d
dist[i][j]+minpath[i][k]+minpath[j][k],这里面dist[i][j]=g,minpath[i][k]=d
因为floyd没更新完 g[i][k] 不一定是最小值,原始数组 d[i][k] 才是 i->k 最小值
shuai shuai shuai
太帅了 啊啊啊 救命
大佬你这个代码在洛谷上过不了。https://www.luogu.com.cn/problem/P6175
洛谷上那道题简单,不要环中的节点路径,只要环的路径长度和,所以直接输出res就行了。AcWing的还需要记录路径,麻烦一些。