FZSZ Online Judge #858. 【四校联考1008】骑行川藏
问题描述
小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一下能写出来了.