模拟赛 路径和

本文讲解了如何通过Floyd算法优化有向图中不经过特定点的最短路径计算,从复杂度O(n^4)降低到O(n^3 log_2 n),并提供了一个C++代码示例。关键在于跳过已删除节点的策略,以解决大规模图问题。

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

题目描述

对于一张有向图,定义 d(u,v,w)d(u,v,w)d(u,v,w) 为从 uuu 号点出发,不经过 vvv 号点,最终到达w号点的最短路径长度。如果不存在这样的路径,d(u,v,w)d(u,v,w)d(u,v,w) 的值为−1−11
你也可以认为 d(u,v,w)d(u,v,w)d(u,v,w) 是删去 vvv 点和其相关的边后,图中 uuuwww 的最短路。
现在给定这张有向图每两个点之间的有向边的长度(如果不存在连边则为−1−11),对于所有满足 1⩽x,y,z⩽n,x≠y,y≠z1 \leqslant x,y,z \leqslant n,x≠y,y≠z1x,y,zn,x=y,y=z的有序数对 (x,y,z)(x,y,z)(x,y,z),求它们 d(x,y,z)d(x,y,z)d(x,y,z) 的和。
1⩽n⩽3001 \leqslant n \leqslant 3001n300
时间限制2s,空间限制512MB。

题解

此题显然可以枚举每个点 iii,删除后利用Floyd算法算出所有的 d(x,i,z)d(x,i,z)d(x,i,z)。时间复杂度 O(n4)O(n^4)O(n4)
根据Floyd的原理,首先枚举最短路径中转点,而枚举中转点的顺序对答案并不影响,所以可以把删点改为枚举中转点时跳过改点。
设现在要求的除去 xxx 号点后的Floyd最终矩阵。若现已知除去区间 [l,r][l,r][l,r] 的点后的矩阵,令 mid=⌊l+r2⌋mid= \lfloor \frac{l+r}{2} \rfloormid=2l+r,区间 [l,mid][l,mid][l,mid] 的答案可以在区间 [l,r][l,r][l,r] 的基础上枚举区间 [mid+1,r][mid+1,r][mid+1,r] 中的点为中转点,更新矩阵。区间 [mid+1,r][mid+1,r][mid+1,r] 同理。最终按原方式统计答案。时间复杂度 O(n3log2n)O(n^3log_2n)O(n3log2n),空间复杂度 O(n3)O(n^3)O(n3)
做此类问题要利用经典算法的原理,思路要开阔。

代码:

#include<stdio.h>
#define R register int
#define L long long
#define I inline void
int dis[1200][301][301];
I Calc(int p,int x,int&n,L&ans){
	for(R i=1;i<=n;i++){
		if(i!=x){
			for(R j=1;j<=n;j++){
				if(j!=x){
					ans+=dis[p][i][j]==5e6?-1:dis[p][i][j];
				}
			}
		}
	}
}
I Solve(int p,int l,int r,int&n,L&ans){
	if(l==r){
		Calc(p,r,n,ans);
	}else{
		int mid=l+r>>1,Ls=p<<1,Rs;
		Rs=Ls|1;
		for(R i=1;i<=n;i++){
			for(R j=1;j<=n;j++){
				dis[Ls][i][j]=dis[Rs][i][j]=dis[p][i][j];
			}
		}
		for(R i=mid+1;i<=r;i++){
			for(R j=1;j<=n;j++){
				for(R k=1;k<=n;k++){
					if(dis[Ls][j][k]>dis[Ls][j][i]+dis[Ls][i][k]){
						dis[Ls][j][k]=dis[Ls][j][i]+dis[Ls][i][k];
					}
				}
			}
		}
		for(R i=l;i<=mid;i++){
			for(R j=1;j<=n;j++){
				for(R k=1;k<=n;k++){
					if(dis[Rs][j][k]>dis[Rs][j][i]+dis[Rs][i][k]){
						dis[Rs][j][k]=dis[Rs][j][i]+dis[Rs][i][k];
					}
				}
			}
		}
		Solve(Ls,l,mid,n,ans);
		Solve(Rs,mid+1,r,n,ans);
	}
}
int main(){
	int n;
	scanf("%d",&n);
	for(R i=1;i<=n;i++){
		for(R j=1;j<=n;j++){
			scanf("%d",dis[1][i]+j);
			if(dis[1][i][j]==-1){
				dis[1][i][j]=5e6;
			}
		}
	}
	L ans=0;
	Solve(1,1,n,n,ans);
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值