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。
输入输出样例
说明
对于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