点这里 |
---|
题意: 给定一个加权有向图,有n个点、m条边以及q条操作。q条操作中,有两种类型0 u
表示将u点做上标记,1 u v
表示计算u点和v的最短距离, 要求是最短路径上包括u、v的所有点都做上标记。 针对不同情况:
- ERROR! At point x: 重复给x做标记则输出该语句。
- ERROR! At path x to y: 要求最短路的两端点并未全做上标记则输出该语句。
- No such path: 两端点都已做上标记,但是这两点间没有路。
- 最短距离: 两点都已做上标记,并且两点间存在最短距离。
题解: n ≤ 300
,所以用Floyd也许能行 。但是Q ≤ 100000
绝对不是令人好受的范围。我也是尝试了很久才找到的最佳办法。
- 多次Floyd: 这是毋庸置疑的,因为要求的最短路上的所有点都必须是做过标记的才行,所以每次有新的标记出现,我们都可能可以更新出新的最短路。但是原生的Floyd模板是不行的,三重循环的时间复杂度太高了。
- Floyd变形: 因此我们要尽量想办法让我们的Floyd适用这个题目,并且时间复杂度能够接受。办法是缩到二重循环。 可以回忆一下Floyd的核心思想是什么,是希望能在两端点u、v之间,找到一个中间节点k,使得
u->k->v
的距离小于u->v
的直接距离。那么我们可以在每次给出一个新的标记k时,去更新所有经过k点的最短路。、 - 标记: 然后你还可能会犯迷糊,虽然但是,按照上面的思路,我在更新的时候,最短路的两端没做过标记怎么办,需要避开不更新吗。答案是每当出现一个新的标记k,就去更新所有可能的最短路。 而为了应对更新的最短路中两端点是为标记的情况,我们只要在输出时另加判断即可。
过程中犯的错:
- 三重Floyd: 办法还没考虑成熟就着急写代码了,所以才出现了上图的那么多错误。掌握Floyd的核心思想的话,改成二重循环即可完成题目。
- Floyd的位置: 有想过将Floyd放在什么位置(出现新标记时、询问两点最短路时)可能会省一些时间复杂度。实际上按照我们上面的思路,每出现一个新的标记,我们就要跑一边Floyd去更新我们的最短路。
- 标记: 在我最初的Floyd函数中,我更新的最短路,所有的点都是做过标记的。这样是不行的,这样的更新是不会完整的。前面我说可能出现更新的最短路中两端点并未标记的情况,只要在输出之前判断两点如果做过标记,我就输出具体数值;如果并未做上标记,我就输出ERROR! At path x to y。
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
const int inf = 0x3f3f3f3f;
int n, m, q, k, a, b, c;
int vis[N];
int dis[N][N];
void floyd(int k){
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
int main(){
while(~scanf("%d%d%d", &n, &m, &q) && (n + m + q)){
if(k) puts("");
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++) dis[i][j] = inf;
vis[i] = dis[i][i] = 0;
}
while(m--){
scanf("%d%d%d", &a, &b, &c);
dis[a][b] = min(dis[a][b], c);
}
printf("Case %d:\n", ++k);
while(q--){
scanf("%d%d", &a, &b);
if(!a){
if(vis[b]){printf("ERROR! At point %d\n", b);continue;}
vis[b] = 1;
floyd(b);
}
else{
scanf("%d", &c);
if(!vis[b] || !vis[c]) printf("ERROR! At path %d to %d\n", b, c);
else{
if(dis[b][c] == inf) printf("No such path\n");
else printf("%d\n", dis[b][c]);
}
}
}
}
return 0;
}