原题链接:
灾后重建 - 洛谷
题目大意:
有n个村庄,村庄间有m条公路,一次地震将连向每个村庄的公路损坏,所以要进行维修,数据保证编号小的村庄维修时间更少,编号大的村庄维修时间更多。后面有q个询问,问是在第几天的时候从x村庄到y村庄我们是否能到达,如果能到达的最小距离是多少。
解题思路:
题目数据给出的是一个稠密图,最大可以是一个全连通图,每两个点之间都有边直连。
最朴素的想法是每读一个查询就对整个新图重新跑一次dijkstra,复杂为O(Q*m*n*logn)=O(Q*n^3*logn),如果换成floyd,复杂度为O(Q*n^3),更好,但依然超时。
其实我们可以只需要完整的进行一次floyd,而不需要Q次,总的复杂度为O(Q+n^3)。
因为每个顶点的修复时间是按顶点编号递增的,floyd的执行过程又是逐步加入点,从第1个点扩展到第n个点,加第k个点时,前面的1~k-1个点都已经执行完毕,当k到达最后的第n个点时,包含所有1~n点的最短路径计算完毕。而在本题,在第t个时刻,小于t时刻的图的顶点的最短路径计算完毕,在t+1时刻,只需要在t时刻计算结果的基础上,继续计算新加入的点(t~t+1时间内修复的村庄)即可。
代码(CPP):
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 310;
const int INF = 0x3fffffff;
const int mod = 1000000007;
struct edge {int v, w;
};
int dis[maxn][maxn];
int t[maxn];
int n, m;/*题目数据给出的是一个稠密图,最大可以是一个全连通图,每两个点之间都有边直连。最朴素的想法是每读一个查询就对整个新图重新跑一次dijkstra,复杂度为O(Q*m*n*logn)=O(Q*n^3*logn),如果换成floyd,复杂度为O(Q*n^3),更好,但依然超时。其实我们可以只需要完整的进行一次floyd,而不需要Q次,总的复杂度为O(Q+n^3)。因为每个顶点的修复时间是按顶点编号递增的,floyd的执行过程又是逐步加入点,从第1个点扩展到第n个点,加第k个点时,前面的1~k-1个点都已经执行完毕,当k到达最后的第n个点时,包含所有1~n点的最短路径计算完毕。而在本题,在第t个时刻,小于t时刻的图的顶点的最短路径计算完毕,在t+1时刻,只需要在t时刻计算结果的基础上,继续计算新加入的点(t~t+1时间内修复的村庄)即可。
*/void solve() {cin >> n >> m;for (int i = 0; i < n; i++) {cin >> t[i];}for (int i = 0; i < n; i++){for (int j = 0; j < n; j++) {if (i != j)dis[i][j] = dis[j][i] = INF;}}while (m--) {int u, v, w;cin >> u >> v >> w;dis[u][v] = dis[v][u] = w;}int q;cin >> q;int k = 0;while (q--) {int u, v, tt;cin >> u >> v >> tt;for (; k < n && t[k] <= tt; k++) { // 将t~t+1时刻内修复的点的计算出来for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (dis[i][k] != INF && dis[k][j] != INF && dis[i][k] + dis[k][j] < dis[i][j]) {dis[i][j] = dis[i][k] + dis[k][j];}}}}if (dis[u][v] == INF || t[u] > tt || t[v] > tt) {cout << -1 << endl;} else {cout << dis[u][v] << endl;}}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}