洛谷 P1462 通往奥格瑞玛的道路(最短路,二分)

10 篇文章 0 订阅
2 篇文章 0 订阅

题目大意:

已知一幅图,从一个点出发到终点。每个点有一个点权值,每条边有一个负权值。问怎么走,可以使得我们从起点到终点时,经过的点权最大值最小,同时边权累计不超过-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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值