思路
刚开始还读错题了,虽然读对题了也没思路。给出无向图,求点1到点N所有路径中,第K+1大的边最小的一条路径的第K+1大的边长。
有一个简单的解法,二分答案,对于每一个答案ans,我们对于每一条边,若超过ans则设为1,小于ans则设为0,求1到n的最短路是否小于k,就可以判断这个答案是否能够得到。
我采用了dp的做法,还可以顺便复习一下dp。我们考虑有一条边,从x点到y点,边权为z。定义距离dis[y][p]为到达y点路径里,第p+1大的边尽可能小时,第p+1大的边权。则我们可以从dis[x][p],dis[x][p-1]递推过来。分两类情况,一类是边(x, y, z)不作为被免费的p条边,那么dis[y][p] = max(dis[x][p], z);一类是边(x, y, z)作为被免费的p条边,那么dis[y][p] = dis[x][p-1]。这样的递推是完备的,使用SPFA不断更新这些点,当更新不再发生时,最短路已得。
碰到的问题
太久不写图论,交了三发,发发爆内存。要注意链式前向星里面head数组的长度是点数,而作为无向图,ver、edge、Next数组的长度都是两倍的边数啊。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, K = 1e3 + 10, P = 2e4 + 10;
const int INF = 0x3f3f3f3f;
int n, p, k;
int head[N], ver[P], tot = 1, edge[P], Next[P];
void add(int a, int b, int w) {
edge[++tot] = w; ver[tot] = b;
Next[tot] = head[a];
head[a] = tot;
}
int dis[N][K];
bool vis[N]; //这个是防止某个元素重复入队
void SPFA() {
queue<int> q;
q.push(1);
memset(vis, false, sizeof(vis));
memset(dis, INF, sizeof(dis));
vis[1] = true;
dis[1][0] = 0;
while(!q.empty()) {
int x = q.front(); q.pop();
vis[x] = false;
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i], z = edge[i];
int w = max(dis[x][0], z);
if (dis[y][0] > w) {
// cout << dis[y][0] << endl;
dis[y][0] = w;
if (!vis[y]) {
q.push(y);
vis[y] = true;
}
}
for (int p = 1; p <= k; p++) {
w = min(max(dis[x][p], z), dis[x][p - 1]); // min(不用z边 / 用z边)
if (dis[y][p] > w) {
dis[y][p] = w;
if (!vis[y]) {
q.push(y);
vis[y] = true;
}
}
}
}
}
}
int main() {
scanf("%d %d %d", &n, &p, &k);
for (int i = 0; i < p; i++) {
int a, b, w;
scanf("%d %d %d", &a, &b, &w);
add(a, b, w);
add(b, a, w);
}
SPFA();
int ans = INF;
for (int i = k; i >= 0; i--) {
ans = min(ans, dis[n][i]);
}
if (ans == INF)
printf("-1\n");
else
printf("%d\n", ans);
}