【最短路 动态规划】洛谷_1948 电话线Telephone Lines

题意

给出一个图,求出其中一条从点 1 1 1到点 N N N的路径,在上面选掉 K K K条边,使得剩下的边中最大的最小。

思路

这个题其实是求出一条 1 1 1 N N N的路径中使得第 K + 1 K+1 K+1的边最小。

解法一:

首先看到字样“最大的最小”,我们可以感觉这个题可以用二分,然后细读一下发现答案满足单调性。
当我们二分出一个 m i d mid mid,可以求出这个图中从 1 1 1 N N N的路径中大于 m i d mid mid的边的个数,如果个数超过了 K K K,说明答案不可行。
求出这个边的个数我们只用把超过 m i d mid mid的边权记为 1 1 1,然后利用 S P F A SPFA SPFA或双端队列 B F S BFS BFS的方法就可以求出了。

解法二:

可以用动态规划,设 f [ u ] [ k ] f[u][k] f[u][k]为从 1 1 1 u u u中已经选了 k k k条路的最优答案,可得:
表示选 f [ v ] [ k + 1 ] = m i n ( f [ v ] [ k + 1 ] , f [ u ] [ k ] ) f[v][k+1]=min(f[v][k+1],f[u][k]) f[v][k+1]=min(f[v][k+1],f[u][k])
表示不选 f [ v ] [ k ] = m i n ( f [ v ] [ k ] , m a x ( f [ u ] [ k ] , e . v ) f[v][k]=min(f[v][k],max(f[u][k],e.v) f[v][k]=min(f[v][k],max(f[u][k],e.v)
其中 v v v表示边的另一个点, e . v e.v e.v表示权值。
因为没有一个确定的顺序 d p dp dp,我们可以用 S P F A SPFA SPFA来转移,相当于取 m i n min min就为松弛操作。

代码

解法一:

#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;

int N, P, K, tot, l, r;
int head[1001], vis[1001], d[1001];
struct node{
    int to, v, next;
}e[20001];

void add(int x, int y, int z) {
    e[++tot].to = y;
    e[tot].v = z;
    e[tot].next = head[x];
    head[x] = tot;
    e[++tot].to = x;
    e[tot].v = z;
    e[tot].next = head[y];
    head[y] = tot;
}

int check(int x) {
    deque<int> q;
    memset(d, 0, sizeof(d));
    memset(vis, 0, sizeof(vis));
    q.push_back(1);
    vis[1] = 1;
    while (q.size()) {
        int u = q.front(), v;
        q.pop_front();
        for (int i = head[u]; i; i = e[i].next) {
            v = e[i].to;
            if (!vis[v] || d[v] >= d[u] + 1) {
                vis[v] = 1;
                //双端队列做法可保证每次取出来的点都是最优的
                if (e[i].v > x) q.push_back(v), d[v] = d[u] + 1;
                else q.push_front(v), d[v] = d[u];
            }
        }
    }
    return d[N] <= K && vis[N];
}

int main() {
    scanf("%d %d %d", &N, &P, &K);
    for (int i = 1, x, y, z; i <= P; i++) {
        scanf("%d %d %d", &x, &y, &z);
        add(x, y, z);
        r = max(r, z);
    }
    l = 1;
    int mid, f = 0;//f记录无解情况
    while (l < r) {
    	mid = (l + r) >> 1;
    	if (check(mid)) r = mid, f = 1;
    	else l = mid + 1;
    }
    printf("%d", f ? r : -1);
}

解法二:

#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;

int N, P, K, tot;
int head[1001], f[1001][1001], v[1001][1001];
struct node{
    int to, v, next;
}e[20001];

void add(int x, int y, int z) {
    e[++tot].to = y;
    e[tot].v = z;
    e[tot].next = head[x];
    head[x] = tot;
    e[++tot].to = x;
    e[tot].v = z;
    e[tot].next = head[y];
    head[y] = tot;
}

void spfa() {
    memset(f, 127 / 3, sizeof(f));
    queue< pair<int, int> > q;
    q.push(make_pair(1, 0));
    v[1][0] = 1;
    f[1][0] = 0;
    pair<int, int> a;
    while (q.size()) {
        a = q.front();
        q.pop();
        v[a.first][a.second] = 0;
        for (int i = head[a.first]; i; i = e[i].next) {
            if (a.second < K && f[a.first][a.second] < f[e[i].to][a.second + 1]) {//use
                f[e[i].to][a.second + 1] = f[a.first][a.second];
                if (!v[e[i].to][a.second + 1]) {
                    v[e[i].to][a.second + 1] = 1;
                    q.push(make_pair(e[i].to, a.second + 1));
                }
            }
            if (max(e[i].v, f[a.first][a.second]) < f[e[i].to][a.second]) {//not use
                f[e[i].to][a.second] = max(e[i].v, f[a.first][a.second]);
                if (!v[e[i].to][a.second]) {
                    v[e[i].to][a.second] = 1;
                    q.push(make_pair(e[i].to, a.second));
                }
            }
        }
    }
}

int main() {
    scanf("%d %d %d", &N, &P, &K);
    for (int i = 1, x, y, z; i <= P; i++) {
        scanf("%d %d %d", &x, &y, &z);
        add(x, y, z);
    }
    spfa();
    printf("%d", f[N][K] == 707406378 ? -1 : f[N][K]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值