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;
}
完结撒花。有问题可以在评论区指出。