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。

 

输入输出样例

输入样例#1: 
4 4 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3
输出样例#1: 
10

说明

对于60%的数据,满足n≤200,m≤10000,b≤200

对于100%的数据,满足n≤10000,m≤50000,b≤1000000000

对于100%的数据,满足ci≤1000000000,fi≤1000000000,可能有两条边连接着相同的城市。(注意细节)

 

这道题值得一写啊。。。我卡了差不多十次。每次都是一点点瑕疵。

 

一开始想着dfs暴力搞定,如你所料,TLE了。。。于是转向求助于dijkstra,看了看书发现最短路可以做这道题,结果是最后骗AFK骗到45分。。。

无奈求助于题解,遂用二分解之。

 

我解释的挺不清楚的,因为我自己也没有理解透彻,这道题比较绕,容易把最大跟最小还有限制条件一下子搞混了,就全乱了。


 

解析:

 

题目有点难理解,先解释一下题目:

对于一个有向图G,有两种信息:一个是每条边的权,一个是每个点的费用。

对于这个给定的有向图G,从1到n的通路是一定的。

限制条件:给定一个值,使得某条1到n的通路的边权之和不大于这个值。

我们假设满足以上限制的这些1到n的通路的集合为S(n)。

对于这个集合S中的每个元素(即每条通路),我们记该路径上点的费用的最大值为另一个集合max(n)。

 

嘿,有了上面这些数学语言的描述作基础,我们可以很简单的理解题意。

我们就是要求出max集合中的最小值。

 

思路大概是这样:

我们首先要得到合法的可达的通路,使得歪嘴哦不死,然后就是对这条通路做限制,使得这条通路对应的max尽可能小。

我们知道,可选的点是一定的,所以本题的答案一定在所有点的集合中,为了得到更小的让歪嘴哦不死的一个max,我们有一个解法。

假设所有可选的点是a1,a2,...an,我们就把它们排个序,从大到小来找一个既使得歪嘴哦不死,又使得max尽量小的点费用。

因为要让他尽可能不死,所以我们用最短路算法来尽量让他活下来。

 

哎,发现了没有,“从大到小来找一个既使得歪嘴哦不死,又使得max尽量小的点费用。”这样一个设定,使得我们可以用二分去解它!

二分的前提是,歪嘴哦不死!

 

我用了一个dij来判断歪嘴哦在当前点费用限制下会不会死:

 1 bool dijkstra(int limit)
 2 {
 3     memset(v,0,sizeof(v));
 4     memset(d,0x3f,sizeof(d));
 5     d[1]=0;
 6     q.push(make_pair(0,1));
 7     while(q.size()){
 8         int index=q.top().second;q.pop();
 9         if(v[index]) continue;
10         v[index]=1;
11         for(int i=head[index];i;i=g[i].next){
12             int y=g[i].ver,z=g[i].edge;
13             if(d[y]>d[index]+z&&c[y]<=limit){
14                 d[y]=d[index]+z;
15                 q.push(make_pair(d[y],y));
16             }
17         }
18     }
19     if(d[n]<=hp) return 1;
20     else return 0;
21 }

这个limit就是我们对这条最短路能走过的点费用最大值的限制。

 

然后是这个二分:

 1 sort(u+1,u+n+1);
 2     int l=1,r=n,ans=c[n];
 3     while(l<=r){
 4         int mid=(l+r)>>1;
 5         if(dijkstra(u[mid]))
 6         {
 7             r=mid-1;ans=u[mid];
 8         }
 9         else l=mid+1;
10     }
11     printf("%d\n",ans);

不做任何关于点费用的限制歪嘴哦都会死的话,那就只能AFK了(他真惨):

1   if(!dijkstra(INF)){
2         printf("AFK\n");return 0;
3     }

 

参考代码:

具体的我就不写注释了,上面已经写的很清楚了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#define N 10010
#define INF 0x3fffffff
using namespace std;
int head[N],d[N*100],tot,n,m,hp;
int u[N*100],c[N*100];
bool v[N*100];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
struct node{
    int ver,next,edge;
}g[N*100];
void add(int x,int y,int val)
{
    g[++tot].ver=y,g[tot].edge=val;
    g[tot].next=head[x],head[x]=tot;
}

bool dijkstra(int limit)
{
    memset(v,0,sizeof(v));
    memset(d,0x3f,sizeof(d));
    d[1]=0;
    q.push(make_pair(0,1));
    while(q.size()){
        int index=q.top().second;q.pop();
        if(v[index]) continue;
        v[index]=1;
        for(int i=head[index];i;i=g[i].next){
            int y=g[i].ver,z=g[i].edge;
            if(d[y]>d[index]+z&&c[y]<=limit){
                d[y]=d[index]+z;
                q.push(make_pair(d[y],y));
            }
        }
    }
    if(d[n]<=hp) return 1;
    else return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&hp);
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);//到达每个点的费用 
        u[i]=c[i];//我们额外搞一个数组来二分
    }
    for(int i=1;i<=m;i++){
        int x,y,val;
        scanf("%d%d%d",&x,&y,&val);//过边损失血量 
        if(x==y) continue;
        add(x,y,val);add(y,x,val);
    }
    if(!dijkstra(INF)){
        printf("AFK\n");return 0;
    }
    sort(u+1,u+n+1);
    int l=1,r=n,ans=c[n];
    while(l<=r){
        int mid=(l+r)>>1;
        if(dijkstra(u[mid]))
        {
            r=mid-1;ans=u[mid];
        }
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

2019-05-31 19:07:02

posted @ 2019-05-31 19:06  DarkValkyrie  阅读(297)  评论(0编辑  收藏  举报