题目背景
地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
题目描述
给出 地区的村庄数 ,村庄编号从 到 ,和所有 条公路的长度,公路是双向的。并给出第 个村庄重建完成的时间 ,你可以认为是同时开始重建并在第 天重建完成,并且在当天即可通车。若 为 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 个询问 ,对于每个询问你要回答在第 天,从村庄 到村庄y的最短路径长度为多少。如果无法找到从 村庄到 村庄的路径,经过若干个已重建完成的村庄,或者村庄 或村庄 在第t天仍未重建完成 ,则需要返回 。
输入输出格式
输入格式:第一行包含两个正整数 ,表示了村庄的数目与公路的数量。
第二行包含 个非负整数 ,表示了每个村庄重建完成的时间,数据保证了 。
接下来 行,每行 个非负整数 , 为不超过 的正整数,表示了有一条连接村庄 与村庄 的道路,长度为 ,保证 ,且对于任意一对村庄只会存在一条道路。
接下来一行也就是 行包含一个正整数 ,表示 个询问。
接下来 行,每行 个非负整数 ,询问在第 天,从村庄 到村庄 的最短路径长度为多少,数据保证了 是不下降的。
输出格式:共 行,对每一个询问 输出对应的答案,即在第 天,从村庄 到村庄 的最短路径长度为多少。如果在第t天无法找到从 村庄到 村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄 在第 天仍未修复完成,则输出 。
输入输出样例
说明
对于 的数据,有 ;
对于 的数据,有 ,其中有 的数据有 且 ;
对于 的数据,有 ;
对于 的数据,有 , , ,所有输入数据涉及整数均不超过 。
此题居然是一道floyd题目!一直以为求最短路径的算法里面floyd是最简单最直接的,但是由于它是求多源最短路径的算法,所以平时用得非常少,似乎也只觉得它只在那种直接告诉你求多源最短路径的题目里出现。但这道题可以说改变了我对floyd的看法,floyd再不是从前的floyd了,而是现在的floyd。。。
首先这是一道比较动态的最短路问题,结点在某个时间才会出现,因此和它相连的边也才会出现。用最朴素的想法,对每次查询,都对其进行一次dijkstra求最短路,于是时间复杂度为O(n²Q)
, 当然会超时了,只过了6个点,60分。
此题正解是floyd,你可能会想,单源O(n²)
的复杂度都解决不了,你个多源O(n³)
的来凑什么热闹。但其实这道题,简直就是为floyd算法所设计的!弄懂这道题,将会对floyd有新的看法。
其实从题意还是可以看出floyd的苗头的,比如,每次查询的起点和终点都不是固定的,那么这最短路其实是求的多源而非单源,单源的话,由于每次都可能换起点,那么每次都要推翻原来的最短路,极难优化。
而多源的floyd,它的思想是,如果以k为中转点,能不能更新点i到点j的距离,能则更新。如果这个k点暂时不能访问呢?这就意味着此时k不能作为中转点。但这并不影响其他点与点之间最短路的求解,因为k这时不能访问,也即不能作为中转点,那么就不以他为中转点来计算,等到后面他可以作为中转点的时候再计算。floyd中转点计算是不分顺序的。
那么,每个点从不能访问到能访问,只有一次,这就意味着我们只需要对每个点进行一次中转计算,那么时间复杂度就是O(n³+Q)
, perfect!
我们应该抓取的特征是:这张图所有的边都给出了,而结点是一个一个冒出来的,且查询是多源最短路径。这么多符合floyd得特征,怎么不用floyd?floyd也可以很动态的好不好。
100分代码
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 205;
int q, s = 0, tim[maxn];
int G[maxn][maxn] = {};
bool ok[maxn] = {}, vis[maxn] = {};
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> tim[i];
}
memset(G, 0x3f, sizeof(G));
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
G[u][v] = G[v][u] = w;
}
cin >> q;
for (int i = 0; i < q; ++i) {
int u, v, t;
cin >> u >> v >> t;
while (tim[s] <= t) {
ok[s] = true;
s++;
}
for (int k = 0; k < n; ++k) {
if (ok[k] && !vis[k]) {
vis[k] = true;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
}
}
}
}
if (ok[u] && ok[v] && G[u][v] != 0x3f3f3f3f) cout << G[u][v] << endl;
else cout << -1 << endl;
}
}
60分朴素代码(其实难写多了)
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
struct Edge {
int to, w, next;
};
const int maxn = 205, maxm = 20005;
const int INF = 0x3f3f3f3f;
int n, m, cnt = 1;
int head[maxn] = {}, ok[maxn] = {};
int vis[maxn] = {}, dis[maxn] = {};
pair<int, int> Time[maxn];
Edge edge[maxm];
void add(int u, int v, int w)
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void dijkstra(int start, int end)
{
dis[start] = 0;
for (int i = 0; i < n; ++i) {
int u = -1, _min = INF;
for (int j = 0; j < n; ++j) {
if (!vis[j] && _min > dis[j]) {
u = j;
_min = dis[j];
}
}
if (u == -1) return;
vis[u] = true;
for (int j = head[u]; j; j = edge[j].next) {
int v = edge[j].to, w = edge[j].w;
if (ok[v] && !vis[v]) {
dis[v] = min(dis[v], dis[u] + w);
}
}
}
return;
}
int main()
{
std::ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> Time[i].first;
Time[i].second = i;
}
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
int q;
cin >> q;
int s = 0;
for (int i = 0; i < q; ++i) {
int u, v, t;
cin >> u >> v >> t;
while (Time[s].first <= t && s < n) {
ok[Time[s].second] = true;
s++;
}
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
if (!(ok[u] && ok[v])) cout << -1 << endl;
else {
dijkstra(u, v);
cout << (dis[v] == INF ? -1 : dis[v]) << endl;
}
}
}