D. Ela and the Wiring Wizard(最短路径/floyd)

题目
参考

题意

给定一个带权无向图,n个点,m条带权无向边。

要从点1走到点n。

走之前,可以执行以下操作
对于连接点u,v,边权为w的边,可以将它从点u移除,并连接到任意一个与v相邻的点(点v也可以与自己相连)。但需要花费的时间为w单位。

可以执行以上操作任意次。

问,执行了上述若干次操作后,从点1走到点n,需要的最短时间是多少(包括修改边时花费的时间)。

思路

定理1:点1到点n的最短路径,经过的边,边权一定相同。

反证法:如果点1到点n的最短路径,存在两条相邻的边,边权不同,w1,w2,不失一般性,我们假设w1<w2,此时从点v1到v3,需要的时间为w1+w2
在这里插入图片描述
而因为w1<w2,如果我们将w1这条边接到v1,v3,那么此时花费的时间为w1+w1<w1+w2。(第一个w1表示修改该边所花费的时间,第二个w1表示从v1走到v3所花费的时间)
在这里插入图片描述
综上,点1到点n的最短路径,经过的所有的边,边权一定是相同的。

定理2:选择一条边,将点1和点n连接到一起,是最优的选择。

证明:根据定理1,点1到点n的最短路径,经过的所有边,边权一定是相同的,我们将边权假设为w。对于相邻的两条边,它们的边权都是w。此时从v1走到v2,需要w+w。
在这里插入图片描述

而如果我们将v1-v2这条边,连接到v1-v3,我们发现,修改路线后,花费的时间也是w+w。(第一个w表示修改该边所花费的时间,第二个w表示从v1走到v3所花费的时间)

在这里插入图片描述
综上,定理2成立。

根据定理2,我们枚举所有边。
对于当前的边(u,v,w),我们尝试将它连接到点1和点n。

  • 直接连接,让点u走到1,点v走到n;或者让点v走到1,点u走到n。
  • 间接连接,让点u,v都走到点i,再让点i分别走到点1和点n。点u,v都走到点i,有两种走法,一种是先走点u,最后走点v;另一种是先走点v,最后走点u。

我们可以用floyd算法,预处理,计算任意两个个点距离的最少边数。

  • 对于直接连接,点u走到点1,需要花费f[u][1]*w的时间;点v走到点n,需要花费f[v][n]*w的时间。最后,点1走到点n,需要花费w*1的时间。
  • 对于间接连接,点u走到点i,需要花费f[u][i]*w的时间,点v走到点i,需要花费1*w的时间(因为此时u和i相邻,v和i相连只需要花费一次修改),点i走到点1和点n,分别花费f[i][1]*w,f[i][n]*w的时间。最后,点1走到点n,需要花费w*1的时间。

详见代码

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pcc pair<char, char>
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn = 510;
const int maxm = 250010;

int n, m;
int f[maxn][maxn];
struct edge {
	int u, v, w;
	edge(){}
	edge(int _u, int _v, int _w):u(_u), v(_v), w(_w) {}
}e[maxm];
int u, v, w;
void solve() {
    scanf("%d%d", &n, &m);
    
    for (int i = 0; i < n; ++i) {
    	for (int j = 0; j < n; ++j) {
    		f[i][j] = n + 1;
		}
		f[i][i] = 0;
	}
    for (int i = 0; i < m; ++i) {
    	scanf("%d%d%d", &u, &v, &w);
    	--u;
    	--v;
    	f[u][v] = f[v][u] = 1;
    	e[i] = edge(u, v, w);
	}
	
	// floyd
	for (int k = 0; k < n; ++k) {
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < n; ++j) {
				f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
			}
		}
	}
	ll ans = inf;
	for (int i = 0; i < m; ++i) {
		u = e[i].u;
		v = e[i].v;
		w = e[i].w;
		
		
		// direct connect
		ans = min(ans, 1LL * w * (f[0][u] + f[v][n-1] + 1));
		ans = min(ans, 1LL * w * (f[0][v] + f[u][n-1] + 1));
		
		// indirect connect
		for (int k = 0; k < n; ++k) {
			ans = min(ans, 1LL * w * (f[v][k] + 1 + f[k][0] + f[k][n-1] + 1));
			ans = min(ans, 1LL * w * (f[u][k] + 1 + f[k][0] + f[k][n-1] + 1));
		}
	}
	printf("%lld\n", ans);
}

int main() {
    int t;
    scanf("%d", &t);
//    t = 1;
    while (t--) {
    	solve();
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值