问题描述
小C非常热衷于挑战自我,国庆假期他准备骑着自行车从福州前往成都再沿川藏线前往拉萨。川藏线的沿途有着非常美丽的风景,但在这一路上也有着很多的艰难险阻,路况变化多端,而小C的体力十分有限,因此在每天的骑行前设定好目的地、同时合理分配好自己的体力是一件非常重要的事情。
可供小C选择的道路构成了一张连通无向图,小C的起点位于1号点,终点位于n号点,每条道路有一个困难度vi,小C定义一条路径的疲劳度为他路上经过的所有道路的困难度的最大值。一开始小C有k点体力,在通过一条道路时,他可以选择消耗若干点体力值,每消耗一点,道路的困难度也会降低1,但一条道路的困难度不能低于0。小C想知道他这次旅程的最小疲劳度。
输入格式
第一行三个非负整数n,m,k,分别表示图的点数,边数以及小C的初始体力值。
接下来m行,每行三个正整数xi,yi,vi,分别表示第i条边的两个端点以及困难度。
输出格式
输出一个整数,表示答案。
样例输入
3 3 1
1 2 3
2 3 4
1 3 5
样例输出
3
数据范围
对于20%的数据,n,m,k,vi<=1000;
对于另外20%的数据,m=n-1;
对于另外20%的数据,k=0;
对于100%的数据,2<=n<=50000,m,k,vi<=50000。
题目链接:http://192.168.68.33/problem/858.
类似问题:luoguP1948 [USACO08JAN]电话线Telephone Lines.
题目链接:https://www.luogu.org/problem/show?pid=1948.
解题报告
这题很显然是道二分题.
首先,先二分一个答案[0,max{vi}],复杂度(logmax{vi})
在对原图的边权进行改造:
(1).对于vi<=mid,建边权为0的新边,
(2).对于vi>mid,建边权为(vi-mid)的新边.
在新图中跑一遍最短路SPFA或堆优化的Dijkstra,
复杂度为O(km)或O(nlogm),
个人推荐堆优化的Dijkstra,复杂度稳定.
这样做可以找到一条最大值为mid且消耗体力最小的最优路径.
若dis[n]<=k,则mid为可行答案,继续二分[l,mid),
反之,mid不可行,继续二分(mid,r].
总复杂度为O(logmax{vi}*km)或O(logmax{vi}*nlogm).
AC代码
#include<cstdio>
#include<iostream>
#include<queue>
#define pa pair<int,int>
#define mp(x,y) make_pair(x,y)
#define FOR(i,s,t) for(register int i=s;i<=t;++i)
#define ll long long
#define INF 2147483647
#define BIG 200011
using namespace std;
priority_queue<pa>heap;
int n,m,k;
int x,y,z,tot,l,r,ans=50000;
int nxt[BIG],to[BIG],las[BIG],w[BIG],dis[BIG],vis[BIG];
inline void add(int x,int y,int z){
nxt[++tot]=las[x];
las[x]=tot;
to[tot]=y;
w[tot]=z;
return;
}
inline int DJ(int x){
FOR(i,2,n)
dis[i]=INF,vis[i]=0;
vis[1]=0;
heap.push(mp(0,1));
int now,u;
while(!heap.empty()){
now=heap.top().second;
heap.pop();
if(vis[now])
continue;
vis[now]=1;
for(register int e=las[now];e;e=nxt[e]){
u=w[e]<x?0:(w[e]-x);
if(dis[to[e]]>dis[now]+u){
dis[to[e]]=dis[now]+u;
heap.push(mp(-dis[to[e]],to[e]));
}
}
}
return dis[n]<=k?1:0;
}
inline void divide(int l,int r){
if(r-l<=3){
FOR(i,l,r)
if(DJ(i))
ans=min(ans,i);
return;
}
int mid=(l+r)>>1;
if(DJ(mid)){
ans=min(ans,mid);
divide(l,mid-1);
}
else
divide(mid+1,r);
return;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
FOR(i,1,m){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
ans=max(z,ans);
}
r=ans;
divide(l,r);
cout<<ans<<endl;
return 0;
}
对于类似问题,做法类似.
二分答案,
改造边权:
(1).vi<=mid,新边权为0.
(2).vi>mid,新边权为1.
跑一边最短路,
复杂度为O(logmax{vi}*km)或O(logmax{vi}*nlogm).
AC代码自己xjbYY一下能写出来了.