1. 程式人生 > 實用技巧 >P1462 通往奧格瑞瑪的道路 題解

P1462 通往奧格瑞瑪的道路 題解

題目傳送門

1.題外話

最近在刷有關圖論,dp的題單~

2.解題意

n個節點,m條雙向邊。每個節點有一個權值\(f[i]\),每個邊有一個邊權(\(edge[i].dis\)),起點編號是1,終點編號是n。讓你求對於每一個b,使得\(1到n\)的最短路小於邊權和小於等於b且使得路徑上經過的最大的點權最小。

3.找思路

很明顯,對於“最大值最小”\(or\)“最小值最大”的問題,考慮二分;

我們二分列舉一個節點限制\(now\),點權f大於now的點不會考慮進路徑,剩下的節點跑一遍最短路;

如果到終點的最短路的dis陣列,也就是最小邊權和小於now,說明當前的這條路徑減少的血量小於當前二分的血量,存在著最多的一次收取的費用的最小值更大的可能

,我們就嘗試使\(r=mid-1\),將now值縮小,繼續二分下去。直到我們的l和r相差為1或者0.說明我們找到了那個最小的滿足條件的點權和。輸出即可。

對於二分的題目,一定要明確自己要二分得到的結果,並且要清楚地知道邊界情況的處理方式。如果想錯了可能你的樣例跑出來是對的,但是其他的一些資料會出鍋。

4.\(Code\)

在這裡用的是spfa。也可以用dij

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
#define re register
#define N 10007
#define inf 2147483646
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,b,num,hea[N],d[N],vis[N],f[N];
int u,v,w;
struct edg{
	int next,to,dis;
}edge[N*20];
inline void add(int from,int to,int dis)
{
	num++;
	edge[num].dis=dis;
	edge[num].to=to;
	edge[num].next=hea[from];
	hea[from]=num;
}
inline bool spfa(int now)
{
	if(now<f[1])return 0;
	queue<int> q;
	for(int i=1;i<=n;i++)
		d[i]=1e9;
	memset(vis,0,sizeof(vis));
	vis[1]=1;
	d[1]=0;
	q.push(1);
	while(!q.empty())
	{
		int k=q.front();q.pop();
		vis[k]=0;
		for(int i=hea[k];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(d[v]>d[k]+edge[i].dis&&f[v]<=now)
			{
				d[v]=d[k]+edge[i].dis;
				if(!vis[v])
				{
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	if(d[n]<b)return 1;
	return 0;
}
int main()
{
	n=read(),m=read(),b=read();
	for(int i=1;i<=n;i++)
		f[i]=read();
	for(int i=1;i<=m;i++)
	{
		u=read(),v=read(),w=read();
		add(u,v,w),add(v,u,w);
	}
	int l=1,r=1e9+1;
	if(!spfa(r))
	{
		printf("AFK\n");
		return 0;
	}
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(spfa(mid))
			r=mid-1;
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}

完結撒花。有問題可以在評論區指出。