单调性:对于要求的所经过的所有城市中最多的一次收取的费用的最小值越小,满足条件的道路就越少。
所以,我们二分经过的所有城市中收取的费用的最大值,然后放到check函数中验证是否可行即可。
但check函数怎么写呢?
我们以经过的边所扣的血量为边权,1为源点跑一边最短路。在最短路过程中用于松弛dis数组的边的终点的点权必须。
如果跑出的,则说明术士在当前限制下无论走什么合法(经过点的点权全部)的路径都无法或者到达终点,当前答案不合法。反之,则合法。
Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define ri register int
using namespace std;
const int MAXN=100020,INF=(int)2e9+1;
int n,m,f[MAXN],u[MAXN],v[MAXN],fst[MAXN],nxt[MAXN],l,r,mid;
long long b,w[MAXN],dis[MAXN];
//边权较大,注意开long long,同时放大INF的取值。
bool book[MAXN];
inline int read()
{
int x=0;
char ch=getchar();
while(ch<'0'||'9'<ch) ch=getchar();
while('0'<=ch&&ch<='9')
{
x=(x <<3)+(x <<1)+(ch-'0');
ch=getchar();
}
return x;
}
struct node{
int num;
long long c;
};
priority_queue<node>Q;
bool operator<(const node &a,const node &b)
{
return a.c > b.c;
}
bool Dijkstra(int minn)//check函数,minn为当前要判定的答案
{
memset(book,0,sizeof(book));
for(ri i=1;i<=n;i++) dis[i]=INF;
dis[1]=0;
if(f[1]>minn) return 0;
//注意如果1的点权>minn,显然任何路径在当前点权大小限制(<=minn)下都是不合法的
Q.push((node){1,0});
while(!Q.empty())
{
int x=Q.top().num; Q.pop();
if(book[x]) continue;
book[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
if(dis[v[k]]>dis[u[k]]+w[k]&&f[v[k]]<=minn)
//用于松弛dis数组的边的起&终点的点权必须<=minn
{
dis[v[k]]=dis[u[k]]+w[k];
Q.push((node){v[k],dis[v[k]]});
}
}
if(dis[n]>=b) return 0;//判断在当前限制下是否存在合法路径使术士能活着到达点n
return 1;
}
int main()
{
n=read(),m=read(),b=(long long)read();
for(ri i=1;i<=n;i++)
{
f[i]=read();
r=max(r,f[i]);
}
m <<=1;
for(ri i=1;i<=m;i+=2)
{
u[i]=read(),v[i]=read(),w[i]=(long long)read();
nxt[i]=fst[u[i]],fst[u[i]]=i;
u[i+1]=v[i],v[i+1]=u[i],w[i+1]=w[i];
nxt[i+1]=fst[u[i+1]],fst[u[i+1]]=i+1;
}
while(l+1<r)//二分
{
mid=(l+r)>>1;
if(Dijkstra(mid)) r=mid;
else l=mid;
//mid合法:r=mid,否则l=mid。
}
for(ri i=l;i<=r;i++)
if(Dijkstra(i)) { cout<<i; return 0; }
cout<<"AFK";
return 0;
}