D. Short Path(floyd应用,上海12月月赛)

博客介绍了如何利用Floyd算法解决一道图论问题,该问题要求构建边使得所有两点间最短路径不变。通过Floyd算法判断是否存在替代路径,从而减少不必要的边。文章讨论了算法实现细节,包括避免重复减边和优化循环次数,同时提供了正确和超时的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linking


题意:

一共有 n n n 个点,现在用 n 2 n^2 n2 矩阵给出了任意两点之间的最短距离 a i j a_{ij} aij
现需要你建几条边,使得所给出的任意两点的最短距离都要满足。
输出所建边权之和的最小值。如果无法满足,输出-1。
在这里插入图片描述

思路:

这道题与众不同的地方在于,已经给出了任意两点的最短距离,只需要判断是否仍存在更短距离(无解情况)或者能由其他路径能够到达(这两个点之间就不用建边)。
其实就是从一个完全图中,筛掉几个两点间的边,但仍要满足最短距离

那么就可以用 f l o y d floyd floyd 来判断,是否存在一个中转点,能够连接其他路径使得当前两点间的距离就是所给的最短距离。如果是,那么当前两点间的直接连边就不需要建,成功的筛掉一个边。
这条边用不到了,因为任何可能用到这条边的路径,这条边都可以由另一条距离仍是最短距离的路径所代替,这条边就没必要存在了。
所以只跑一遍 Floyd 便可以解决问题

需要注意的点:

  1. 在跑Floyd的时候,如果判断到有一个边可以由其他路径代替,需要先把这个边标记下来,最后再处理,而不是直接减去这个边权。因为一条边可能有多条替代路径,每次都减就重复了。

  2. Floyd更新的时候,如果 k 和 i 相等或者 k 和 j 相等了,那么就相当于用一条边来更新比较一条边,这样的话肯定是相等的,但不是由其他路径来代替的。所以这种情况不满足,需要跳过。


和队友讨论的做法是这样的:
先将所有的边按照边权从小到大排序,遍历一遍。

  • 如果当前边的两个端点是可通的,并且距离就是最短距离,那么这个边就没必要加,跳过。
  • 如果不是,那么就用把这个边加上,用这两个点分别作为中转点更新其他所有点。

这里用到了一个小技巧,就是加上一个点没必要重新跑一遍Floyd,三种循环。因为只是这一个点改变了,那么只需要用这个点作为中转点更新其余所有与其相连的点就好。这样便只需要两种循环。
同理如果改变了一个边(注意只能把这个边权改小,不能改大!因为改大之后,其余之前由小边权更新的点回不去了,改小的话还能够更新其他点),只需要把两个端点分别作为中转点来更新与其相连的点就好了。

虽然这样做复杂度还是超,但是和队友一起回顾了Floyd的这个技巧,也是一件值得的事!


Code:

正解:

const int N = 5010, mod = 1e9+7;
int T, n, m;
int a[N];
int dist[N][N];
int f[N][N];

signed main(){
	cin>>n;
	
	int ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			
			cin>>dist[i][j];
			if(j>i) ans+=dist[i][j];
		}
	
	int flag=0;
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				if(i==k || j==k) continue;
				if(dist[i][j] > dist[i][k]+dist[k][j]) flag=1;
				else if(dist[i][j] == dist[i][k]+dist[k][j]) f[i][j]=1;
			}
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			if(f[i][j]) ans-=dist[i][j];
	
	if(flag) cout<<-1;
	else cout<<ans;
	
	return 0;
}

和队友讨论的超时做法:

const int N = 250010, mod = 1e9+7;
int T, n, m;
struct ST{
	int dis, x, y;
}a[N];
int dist[510][510];
int d[510][510];

bool cmp(ST a, ST b){
	return a.dis<b.dis;
}

void floyd(int k)
{
	for(int i=1;i<=n;i++)
	{
		if(dist[i][k]>=0x3f3f3f3f) continue; 
		for(int j=1;j<=n;j++)
		{
			if(dist[i][k]+dist[k][j]<dist[i][j])
				dist[i][j]=dist[i][k]+dist[k][j], dist[j][i]=dist[i][j];
		}
	}
} 

signed main(){
    Ios;
    
	cin>>n;
	
	int cnt=0;
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int x;cin>>x;
			if(j>i) a[++cnt]={x,i,j};
		}
	}
	
	mem(dist,0x3f);
	
	sort(a+1,a+cnt+1,cmp);
	int ans=0, flag=0;
	for(int i=1;i<=cnt;i++)
	{
		int dis=a[i].dis, x=a[i].x, y=a[i].y;
		if(dist[x][y] == dis) continue;
		else if(dist[x][y] < dis) flag=1;
		ans += dis;
		dist[x][y] = dist[y][x] = dis;
		floyd(x), floyd(y);
	}
	if(flag) cout<<-1;
	else cout<<ans;
	
	return 0;
}

很好的一道图论题,考察了对Floyd的理解和运用。
Floyd并不是简单的用于求任意两点的最短路径!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值