1018 Public Bike Management (30分)

There is a public bike service in Hangzhou City which provides great convenience to the tourists from all over the world. One may rent a bike at any station and return it to any other stations in the city.

The Public Bike Management Center (PBMC) keeps monitoring the real-time capacity of all the stations. A station is said to be in perfect condition if it is exactly half-full. If a station is full or empty, PBMC will collect or send bikes to adjust the condition of that station to perfect. And more, all the stations on the way will be adjusted as well.

When a problem station is reported, PBMC will always choose the shortest path to reach that station. If there are more than one shortest path, the one that requires the least number of bikes sent from PBMC will be chosen.

The above figure illustrates an example. The stations are represented by vertices and the roads correspond to the edges. The number on an edge is the time taken to reach one end station from another. The number written inside a vertex S is the current number of bikes stored at S. Given that the maximum capacity of each station is 10. To solve the problem at S​3​​, we have 2 different shortest paths:

  1. PBMC -> S​1​​ -> S​3​​. In this case, 4 bikes must be sent from PBMC, because we can collect 1 bike from S​1​​ and then take 5 bikes to S​3​​, so that both stations will be in perfect conditions.

  2. PBMC -> S​2​​ -> S​3​​. This path requires the same time as path 1, but only 3 bikes sent from PBMC and hence is the one that will be chosen.

Input Specification:

Each input file contains one test case. For each case, the first line contains 4 numbers: C​max​​ (≤100), always an even number, is the maximum capacity of each station; N (≤500), the total number of stations; S​p​​, the index of the problem station (the stations are numbered from 1 to N, and PBMC is represented by the vertex 0); and M, the number of roads. The second line contains N non-negative numbers C​i​​ (i=1,⋯,N) where each C​i​​ is the current number of bikes at S​i​​ respectively. Then M lines follow, each contains 3 numbers: S​i​​, S​j​​, and T​ij​​ which describe the time T​ij​​ taken to move betwen stations S​i​​ and S​j​​. All the numbers in a line are separated by a space.

Output Specification:

For each test case, print your results in one line. First output the number of bikes that PBMC must send. Then after one space, output the path in the format: 0−>S​1​​−>⋯−>S​p​​. Finally after another space, output the number of bikes that we must take back to PBMC after the condition of S​p​​ is adjusted to perfect.

Note that if such a path is not unique, output the one that requires minimum number of bikes that we must take back to PBMC. The judge's data guarantee that such a path is unique.

Sample Input:

10 3 3 5
6 7 0
0 1 1
0 2 1
0 3 3
1 3 1
2 3 1

 

Sample Output:

3 0->2->3 0

1、理解题意

       本题需要输出从PBMC携带的自行车数量、最短路径、到达问题车站后需要带回的自行车数量。如果最短路径有多条,那么选择从PBMC携带的自行车数目最少的。如果仍然有多条,那么选择最后从问题车站带回了自行车数目最少的。沿途所有车站在前往问题车站的过程中也要进行调整。

2、解题思路

(1)第一标尺:距离dis

         第二标尺:从PBMC到当前车站必须携带的自行车数量need

         第三标尺:到达当前车站时,手上多余的自行车数量remain。

为了简便代码。可以把每个点的点权(自行车数量)都减去Cmax/2,通过点权的正负来直接判断当前车站是否需要补给还是需要带走额外的车辆。

(2)当前结点的点权weight与它的need和remain之间的关系如下:

	int id=tempPath[i];
			if(weight[id]>0){
				remain+=weight[id];
			}else{
				if(remain>abs(weight[id])){
					
					remain-=abs(weight[id]);
				}else{
					need+=abs(weight[id])-remain;
					remain=0;
				}
				
			}

由于点权之间的计算比较复杂,本题应该使用Dijkstra+DFS的写法求解。代码如下:

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=510;
const int inf=1000000000;

int n,G[maxn][maxn];
bool vis[maxn]={false};
int dis[maxn];
int weight[maxn];
int minNeed=inf,minRemain=inf;
vector<int> pre[maxn];
vector<int> tempPath,path;
int Cmax,Sp,M;


void Dijkstra(int s){
	fill(dis,dis+maxn,inf);
	dis[s]=0;
	for(int i=0;i<=n;i++){
		int u=-1,min=inf;
		for(int j=0;j<=n;j++){
			if(vis[j]==false&&dis[j]<min){
				u=j;
				min=dis[j];
			}
		}
		if(u==-1) return ;
		vis[u]=true;
		for(int v=0;v<=n;v++){
			if(vis[v]==false&&G[u][v]!=inf){
				if(dis[u]+G[u][v]<dis[v]){
					dis[v]=dis[u]+G[u][v];
					pre[v].clear();
					pre[v].push_back(u);
				}else if(dis[u]+G[u][v]==dis[v]){
					pre[v].push_back(u);
				}
			}
		}
	}
}

void DFS(int v){
	if(v==0){
		tempPath.push_back(v);
		int need=0,remain=0;
		for(int i=tempPath.size()-1;i>=0;i--){
			int id=tempPath[i];
			if(weight[id]>0){
				remain+=weight[id];
			}else{
				if(remain>abs(weight[id])){
					
					remain-=abs(weight[id]);
				}else{
					need+=abs(weight[id])-remain;
					remain=0;
				}
				
			}
		}
		if(need<minNeed){
			minNeed=need;
			minRemain=remain;
			path=tempPath;
		}else if(need==minNeed&&remain<minRemain){
			minRemain=remain;
			path=tempPath;
		}
		tempPath.pop_back();
		return;
	}
	tempPath.push_back(v);
	for(int i=0;i<pre[v].size();i++){
		DFS(pre[v][i]); 
	}
	tempPath.pop_back();
}
int main(){
	scanf("%d %d %d %d",&Cmax,&n,&Sp,&M);
	for(int i=1;i<=n;i++){
		scanf("%d",&weight[i]);
		weight[i]-=Cmax/2;
	}
        int u,v;
	fill(G[0],G[0]+maxn*maxn,inf);
	for(int i=0;i<M;i++){
		scanf("%d %d",&u,&v);
		scanf("%d",&G[u][v]);//边权的输入一定要和两个点的输入分开来,否则会导致段错误
		G[v][u]=G[u][v];
	}
	Dijkstra(0);
//	printf("%d",dis[Sp]);
	DFS(Sp);
	printf("%d ",minNeed);
	for(int i=path.size()-1;i>=0;i--){
		printf("%d",path[i]);
		if(i>0) printf("->");
	}
	printf(" %d",minRemain);
	return 0;
}

3、知识总结

(1)Dijkstra+DFS

        当Dijkstra算法中的第二尺度是路径上边权、或点权之和时,例如路径上的花费之和最小、路径上点权之和最小、最短路径的条数都体现了和的要求,可参考PATA1003。如果出现一些逻辑更为复杂的计算边权或点权的方式,此时可以使用一种更为通用又模板化的解决方法--Dijkstra+DFS。简单的来讲就是,更好地处理每个结点v的前驱结点pre[v]。此方法一共有两个步骤:

1)使用笔者一次特拉算法记录所有最短路径(只考虑距离)

        因为此时要记录所有最短路径。每个结点都会存在多个前驱结点。原先pre数组只能记录一个前驱结点的方法将不再适用。为了适应多个前驱的情况,需将pre数组定义为Vector类型:Vector<int> pre[maxn]。在更新dis[v]的过程中有两种情况:

//第一种情况:
if(dis[u]+G[u][v]<dis[v]){
    dis[v]=dis[u]+G[u][v];
    pre[v].clear();//如果pre数组中已经存放了若干节点,此处也应当先清空,然后再添加u
    pre[v].push_back(u);
}
//第二种情况
if(dis[u]+G[u][v]==dis[v]){//说明以u为中介点可以找到一条相同距离的路径。因此v的前驱结点需要在原先的基础上添加上u,而不必清空pre[v]。
    pre[v].push_back(u);
}

2)遍历所有最短路径,找出一条使第二标尺最优的路径(因为在给定一条路径的情况下,针对这条路径的信息都可以通过边权和点权计算出来)

        由于每个结点的前驱结点可能有多个,遍历的过程就会形成一棵递归树。对这棵树进行遍历时,每次到达叶子结点就会产生一条完整的最短路径。因此每得到一条完整路径就可以对这条路径计算其第二标尺的值,令其与当前第二标尺的最优值进行比较。如果比当前最优值更优,则更新最优值,并用这条路径覆盖当前的最优路径。当所有最短路径都遍历完毕后,就可得到最优第二标尺和最优路径。所以DFS递归函数包含:作为全局变量的第二标尺最优值optValue、记录最优路径的数组path(用vector存储)、临时记录DFS遍历到叶子结点时的路径tempPath(用vector存储):

vector<int> pre[maxn];
vector<int> tempPath,path;
int optValue;

void DFS(int v){
    //递归边界
	if(v==s){//如果到达叶子结点(即路径起点)
		tempPath.push_back(v);//将起点0加入到临时路径的最后面:叶子节点没有办法通过递归式中的写法加入tempPath。
		int value;
		计算路径tempPath上的value值;
		if(value优于optValue){
			optValue=value;
			path=tempPath;
		}
		tempPath.pop_back();//将刚加入的结点删除
		return;
	}
    //递归式
	tempPath.push_back(v);//将当前访问结点加入临时路径的最后面
	for(int i=0;i<pre[v].size();i++){
		DFS(pre[v][i]); //对结点v的前驱结点进行递归
	}
	tempPath.pop_back();//遍历完所有前驱结点,将当前结点删除
}

        由于递归的原因,存放在tempPath中的路径结点是逆序的,因此访问结点需要倒着进行。于是在计算路径tempPath上的value值过程中有:

//例如求边权之和
int value=0;
for(int i=tempPath.size()-1;i>=0;i--){
    int id=tempPath[i],idNext=tempPath[i-1];
    value+=V[id][idNext];
}

//求点权之和
int value=0;
for(int i=tempPath.size()-1;i>=0;i--){
    int id=tempPath[i];
    value+=w[id];
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值