题目大意:
已知一幅图,从一个点出发到终点。每个点有一个点权值,每条边有一个负权值。问怎么走,可以使得我们从起点到终点时,经过的点权最大值最小,同时边权累计不超过-b,其中b是负数。
解题思路:
这种是经典的minimax问题,什么最小当中找最大,最大当中找最小。假设这里没有b的约束,同时这里的点权变为边权的话,我们可以生成一个最小(大)生成树,生成树上的两个点的路径的边权最小(大)值即为所求。但是这里需要满足约束,同时是点权,我们这样子考虑。
首先:边权都是负值,我们知道迪杰斯特拉最短路不能用于跑带负权的图,但是边权都是负值的话,我们可以考虑把所有负数都看为正数。所以在这里我们跑的约束变为,边权之和不能超过-b,其中b为负数。
其次:我们怎么解决这个路径中的点权最大值最小呢?最naive的想法就是,从小到大的点权都跑一遍迪杰斯特拉。但是复杂度超了,这里我们使用二分的思想,比如现在我们允许的图中的点权值为W,即点权比W大的点我们都不考虑,假设在这种情况下跑迪杰斯特拉可以满足边权约束,这时候我们把W缩小,假若不能满足边权约束,我们就把边权放大。一直迭代,直到二分的上下边界重合即可。在这里,我们的迪杰斯特拉算法需要修改一下,我们在不考虑大于点权W的点的时候,在迪杰斯特拉的优先队列里面推点进去的时候,点权大于W的我们不要推进去即可。
废话:
(1)很大的一个启发是:迪杰斯特拉算法中,我们不推一些点进入优先队列的思想。这点有点类似于fluery算法走欧拉回路的时候,不考虑桥的点,那么我们任意选择的时候选择哪些点时候的思想。
(2)边权都是负值,我们依然可以使用迪杰斯特拉
(3)二分在这里用的比较新颖,我们是根据迪杰斯特拉跑的结果去推边界。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,b;
const int MAXN=1e4+10;
vector<int> poiwei(MAXN);
vector<vector<pair<int,int>>> gra(MAXN);
int l,r;
const int INF=1e9+10;
vector<int> dist(MAXN);
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
int disj(int money){
dist.assign(MAXN,INF);
pq=priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>();
int u=0;
dist[u]=0;
pq.push(make_pair(dist[u],u));
while(!pq.empty()){
pair<int,int> front=pq.top();pq.pop();
int u=front.second;int udis=front.first;
if(udis>dist[u])continue;
for(int i=0;i<(int)gra[u].size();i++){
int nx=gra[u][i].first;
int wei=gra[u][i].second;
if(poiwei[nx]>money)continue;
if(dist[nx]>dist[u]+wei){
dist[nx]=dist[u]+wei;
pq.push(make_pair(dist[nx],nx));
}
}
}
if(dist[n-1]>=b)return 0;
else return 1;
}
int32_t main(){
cin>>n>>m>>b;
l=INF;
r=-1;
for(int i=0;i<n;i++){
cin>>poiwei[i];
l=min(poiwei[i],l);
r=max(poiwei[i],r);
}
for(int i=0;i<m;i++){
int aa,bb,w;cin>>aa>>bb>>w;
aa-=1;bb-=1;
gra[aa].emplace_back(make_pair(bb,w));
gra[bb].emplace_back(make_pair(aa,w));
}
if(!disj(r)){
cout<<"AFK"<<endl;
return 0;
}
while(l<r){
int m=(r-l)/2+l;
if(disj(m)){
r=m;
}else l=m+1;
}
assert(l==r);
cout<<l<<endl;
return 0;
}