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 秒,稳稳过了。
另外还有几个需要注意的细节:
- 注意是总边数而不是最短路数啊!!!!!蒟蒻一开始理解错了浪费整整半小时。
- 注意存图数组以及最短路数组的初值,在横纵坐标相等时赋值为 \(0\),其它情况赋值为无限大(边权大小没给实在让人纠结,希望修改一下题面增添一下)。
- 注意三重循环区分清楚各个下标的含义。
- 累加 \(sum\) 数组时一定要注意特判 \(j = k\) 的情况并跳出。
- 点点之间不一定连通,最后输出答案需要特判,如果是无穷大则输出 \(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;
}

浙公网安备 33010602011771号