题目大意:有一个n个点m条边的图,(可能有重边),现可以选择任意一条边,使其与一个点断开连接,并连接该点的一个相邻点(可以自己对自己连边),费用为边权,这样的操作可以进行无数次,操作完后再跑一边最短路,问最短路+移边花费最小是多少。
思路:首先我们可以发现只有移1一条边直连1和n的时候才是最短的,因为移边的时候我们是将边的一个点移到他的相邻点上的,那么移一条边到一个点的花费就等于将这条边离目标点近的那个点开始,走最短路到目标点的路径长度*边权,那么如果我们把一条边移到了原先的最短路上,如果这条边边权比最短路上其他边权小,那么肯定要把这条边移到1,n之间,也就是替换原先的最短路,最终花费最小,如果这条边比最短路上的某一条边边权大,那么肯定移动那个边连接1,n最终花费最小。在下图样例中,最优策略是将1,2之间的边连到1,3,最后的最短路是9,
再看第三个样例,最优策略是将2,5移到2,6移到2,7移到7,7移到7,1移到1,4移到1,8,再走一遍等于7*22
综上,我们的得出的答案就是枚举每一条边i,j,将它连接1,n,有两种方案,一种儒如样例三的花费就是找到一个拐点k,找到k离i,j最近的距离再加一次k连k自己的花费,再加上k连1,k连n的花费。还有一种是如样例1,直接找1离i,j的最近距离和n离i,j的最近距离,因为点数很少,所以求距离和答案都可以用floyd实现
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
const int M = 250005, N = 505;
typedef long long ll;
const ll INF = 1e18;
ll ma[N][N];
ll dis[N][N];
int n, m;
void init()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i != j)
ma[i][j] = dis[i][j] = INF;//初始化边权和边长为最大值
else
ma[i][j] = dis[i][j] = 0;//初始化点到点的边长为0
}
}
}
int main()
{
int t;
cin >> t;
while (t--)
{
scanf_s("%d%d", &n, &m);
init();
for (int i = 1; i <= m; i++)
{
ll u, v, w;
scanf_s("%lld%lld%lld", &u, &v, &w);
ma[u][v] = ma[v][u] = min(w, ma[u][v]);//储存点的边权,处理重边
dis[u][v] = dis[v][u] = 1;//储存点的距离
}
for (int k = 1; k <= n; k++)
{
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]);
//floyd求出每两个点之间的距离
}
}
}
ll ans = INF;
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
if (i != k && ma[i][k] < INF)
{
ans = min(ans, ma[k][i] * (dis[1][k] + dis[i][n] + 1));//枚举每一条边,求出将这边移到1和n之间后的花费+走一遍的花费
for (int j = 1; j <= n; j++)
{
ans = min(ans, ma[k][i] * (1 + min(dis[k][j], dis[j][i]) + 1 + dis[1][j] + dis[j][n]));
//枚举每一个点,求出以该点作为中转点进行移边+走一遍的花费
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}