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一下能写出来了.

  

 

posted @ 2017-10-17 08:33  Stump  阅读(455)  评论(0编辑  收藏  举报