luogu P2103 道路值守 题解

原题链接

模拟测试做到这道题,考的是 Floyd 算法的应用,蒟蒻调了一个半小时才熬出来。今天宜:写题解、一遍通过审核,所以攒攒人品顺便巩固一下这道好题。

求审核大大通过啦~~~

题意(简洁向)

求任意两点间所有可能的最短路的所经过的总边数。

思路(详细向)

无论是空间限制、时间限制还是题面都在提示着我们使用多源最短路算法:Floyd。于是我们先使用 Floyd 求出多源最短距离。

难点就在于如何求可能的总边数,如果做过最短路计数的大佬肯定有些想法。

在这里我们设 \(u\)\(v\) 之间最短距离为 \(dp(u,v)\)\(u\)\(v\) 之间的边记为 \((u,v)\),边权为 \(w(u,v)\),若存在一个点 \(k\),使得 \(dp(u,k)+w(k,v)=dp(u,v)\),则边 \((u,v)\) 一定是从 \(u\)\(v\) 可选择的最短路,将其逐一累加在 \(sum(v)\) 中,表示以 \(u\) 为起点,\(v\) 为终点,到达点 \(v\) 的最后一条边有几种可能。

接下来处理最短路计数。假设存在点 \(k\),使得 \(dis(i,k)+dis(k,j)=dis(i,j)\),那么 \(k\) 是从 \(i\)\(j\) 的某条最短路径上经过的点。我们又通过前面的计算得道,从 \(i\)\(k\) 的最短路上,到达 \(k\) 的最后一条边有 \(sum(k)\) 种可能,这 \(sum(k)\) 条边也都是从 \(i\)\(j\) 可以选择的道路。设 \(cnt(i,j)\)\(i\)\(j\) 可能最短路的总边数,那么 \(cnt(i,j)=\sum sum(k)\)

枚举所有的起点、终点、途径点,重复如上处理即可。数据范围 \(n\) 在 500 以内,内存限制 125M(模拟赛开了 1G。。。),使用邻接矩阵存图完全没问题。算法复杂度瓶颈在 Floyd 和后面的遍历,为 \(O(n^3)\),时限十分宽松,给到了 3 秒,稳稳过了。

另外还有几个需要注意的细节:

  1. 注意是总边数而不是最短路数啊!!!!!蒟蒻一开始理解错了浪费整整半小时。
  2. 注意存图数组以及最短路数组的初值,在横纵坐标相等时赋值为 \(0\),其它情况赋值为无限大(边权大小没给实在让人纠结,希望修改一下题面增添一下)。
  3. 注意三重循环区分清楚各个下标的含义。
  4. 累加 \(sum\) 数组时一定要注意特判 \(j = k\) 的情况并跳出。
  5. 点点之间不一定连通,最后输出答案需要特判,如果是无穷大则输出 \(0\),因为这个没注意到丢了两个点。。。

代码(丑陋向)

#include<bits/stdc++.h>
#define int long long //因为不知道权值数据范围所以只好这么整。。。
using namespace std;
const int maxn = 502;
inline int Read();
int n,m,cnt[maxn][maxn],G[maxn][maxn];
int dp[maxn][maxn],sum[maxn];
signed main(){
  n = Read(); m = Read();
  for(int i = 1;i <= n;++i){
  	for(int j = 1;j <= n;++j){
  	  if(i == j) G[i][j] = dp[i][j] = 0; //注意赋初值
  	  else G[i][j] = dp[i][j] = 1e10; 
      //1e10 有些保守了,虽然最终是对的
	}
  }
  for(int i = 1;i <= m;++i){
  	int u = Read(),v = Read(),w = Read();
	G[u][v] = w,G[v][u] = w; 
	dp[u][v] = w;dp[v][u] = w;
  }
  for(int k = 1;k <= n;++k) // Floyd 模板,注意 k、i、j 顺序
  	for(int i = 1;i <= n;++i)
	  for(int j = 1;j <= n;++j)
		  dp[i][j] = min(dp[i][k] + dp[k][j],dp[i][j]);
		  
  for(int i = 1;i <= n;++i){ //起 
  	memset(dep,0,sizeof(dep));
	for(int j = 1;j <= n;++j) //过 
	  for(int k = 1;k <= n;++k){ //终 
	    if(j == k) continue; //attention
	    if(dp[i][j] + G[j][k] == dp[i][k])
	      ++sum[k]; //累加可经边数
	  }
  	for(int j = 1;j <= n;++j){
	  for(int k = 1;k <= n;++k){
	  	if(dp[i][j] + dp[j][k] == dp[i][k]) //最短路计数
		  cnt[i][k] += sum[j];
	  }	
	}
  }
  for(int i = 1;i < n;++i)
    for(int j = i + 1;j <= n;++j)
      if(dp[i][j] == 1e10) printf("0 "); //特判!!!!
      else printf("%lld ",cnt[i][j]);
  return 0;
}
inline int Read(){
  char c = getchar();
  int x = 0,f = 1;
  while(c < '0' || c > '9'){
    if(c == '-') f = -1;
    c = getchar();
  }
  while(c >= '0' && c <= '9'){
    x = x * 10 + c - '0';
    c = getchar();
  }
  return x * f;
}
posted @ 2023-08-23 09:35  CultReborn  阅读(13)  评论(0)    收藏  举报