P1462 通往奥格瑞玛的道路【最大值中的最小】

题目

https://www.luogu.com.cn/problem/P1462

题目背景

在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量

有一天他醒来后发现自己居然到了联盟的主城暴风城

在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛

题目描述

在艾泽拉斯,有n个城市。编号为1,2,3,...,n。

城市之间有m条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。

每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。

假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。

歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少

输入格式

第一行3个正整数,n,m,b。分别表示有n个城市,m条公路,歪嘴哦的血量为b。

接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。

再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,或者从城市bi到城市ai,会损失ci的血量。

输出格式

仅一个整数,表示歪嘴哦交费最多的一次的最小值。

如果他无法到达奥格瑞玛,输出AFK。

 

 分析

这个题极其的……

他所经过的所有城市中最多的一次收取的费用的最小值是多少:翻译如下:

他可能有多条路径能够回城,那么求的就是这些路径中的每一条路径 经过的城市中收费最多的数字,现在要求这个数字最小可以是多少

思路

至于在路上他的血够不够,我们可以将路上失去的血量视为路径的长度,然后使用Dijkstra来判断他的血够不够回城

接下来的问题就是寻找最小值的问题了

具体的思路就是我们设置一个花费最大值变量cost,它必须是一条可行路径上费用最大的值,该路径上节点的费用必须比它小

在使用Dijkstra判断血够不够的同时来判断该路径上有没有节点的费用比cost大,有的话说明我们自己设得这个cost值偏大,于是把cost改小一点继续。

这样一来初步得思路就是我们让cost从1遍历到所有节点费用的最大值,一旦不存在节点的费用比cost大,那么此时的cost就是多条可行路径中经过城市中收费最多的数字里最小的一个,也就是最终的答案

但是cost从1遍历到所有节点费用的最大值,每遍都要执行一次Dijkstra,可能会超时,所以我们使用二分搜索,来加快cost的确定速度

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f 
#define maxn 10001
#define maxm 50001
using namespace std;
struct edge
{
    int to;
    int dis;
    int next;
}e[maxm*2];
struct node
{
    int dis;
    int pos;
    bool operator<(const node &x)const
    {
        return dis > x.dis;
    }
};
int n, m, b;
int bb[maxn], cc[maxn], head[maxn], dis[maxn], vis[maxn], cnt = 0;
void addedge(int u, int v, int  w)
{
    cnt++;
    e[cnt].dis = w;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}
int check(int cost)
{
    if (cost < bb[1]||cost<bb[n])return 0;//由于是从起点出发,到终点结束,如果cost一开始就比这两个点的费用小,那么cost就不是该路径中收费最多的,直接返回false
    memset(dis, inf, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[1] = 0;
    /*for (long long i = 1; i <= n; i++)
        if (bb[i]>cost)vis[i] = true;
        else vis[i] = false;*/
    priority_queue<node>q;
    q.push({0,1});
    while (!q.empty())
    {
        int x = q.top().pos; q.pop();
        if (vis[x])continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if (bb[y]>cost)continue;//因为下面的if是使用节点x作为中转节点来更新起点s到其它点(y)的距离的,所以要判断y点的费用是不是比cost小(保证cost的费用是在最大的)
            if (dis[y]>dis[x] + e[i].dis)
            {
                dis[y] = dis[x] + e[i].dis;
                q.push({ dis[y],y });        
            }
        }
    }
    if (dis[n] <= b)return 1;//当该条路径完毕后,看看需要的最小血量与人拥有的血量大小关系,判断能不能活着回去
    else return 0;
}

int main()
{
    scanf("%d%d%d", &n, &m, &b);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &bb[i]);
        cc[i] = bb[i];
    }
    sort(cc + 1, cc + n + 1);//二分要排序
    for (int i = 0; i < m; i++)
    {
        int ta, tb, tc;
        scanf("%d%d%d", &ta, &tb, &tc);
        addedge(ta, tb, tc);
        addedge(tb, ta, tc);
    }
    int l = 1, r = n, mid;
    int ans = -1;
    while (l <= r)
    {
        mid = (l + r) / 2;
        if (check(cc[mid]))
        {
            ans = cc[mid];
            r = mid - 1;
        }
        else l = mid + 1;
    }
    if (ans == -1)printf("AFK\n");
    else printf("%lld", ans);

}
posted @ 2020-06-16 19:16  Jason66661010  阅读(320)  评论(0编辑  收藏  举报