codeforce_exercise_r29

行车路线(csp201712-4) :

问题描述

题目简述

小明和小芳出去乡村玩,小明负责开车,小芳来导航。
小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走s公里小明会增加s2的疲劳度。
例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为(2+2)2+2+22=16+2+4=22。
现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。

输入/输出格式

输入格式:
输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n编号,小明需要开车从1号路口到n号路口。
接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1号路口和n号路口是连通的。
输出格式:
输出一个整数,表示最优路线下小明的疲劳度。

样例

输入样例:
6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
输出样例:
76

问题分析

解题思路

差不多能看出这是一个dijkstra算法的题目。但是这个题在边权上做了一些文章,就是小路如果在相连的情况下,其所累计的疲劳值是和前面的小路的路程有关的。这样,直接使用dijkstra算法就没法表示这种情况了。因此,可以额外记录一个每个点之前连续走过的小路长度。这样,一旦从该点出发走小路,其增加的疲劳值就可计算了。总体直接跑dijkstra即可完成。

参考代码
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;

const long long inf=30000000005;

class edge
{
public:
	int index;
	int from;
	int to;
	int weight;
	edge(int lx,int u,int v,int w)
	{
		index=lx;
		from=u;
		to=v;
		weight=w;
	}
};

class heapnode
{
public:
	int index;
	long long weight;
	heapnode(int id,long long w)
	{
		index=id;
		weight=w;
	}
	bool operator <(const heapnode& hn) const
	{
		return weight>hn.weight;
	}
};

vector<edge> edges;
vector<int> points[510];
priority_queue<heapnode> h;
int vis[510];
long long pre_length[510];
long long path_length[510]; 
int n,m;

void init()
{
	edges.clear();
	for(int i=0;i<510;i++)
	{
		points[i].clear();
		path_length[i]=inf;
	}
	while(!h.empty()) h.pop();
	memset(vis,0,sizeof(vis));
	memset(pre_length,0,sizeof(pre_length));
}

void addedge(int lx,int u,int v,int w)
{
	edge e1(lx,u,v,w);
	edge e2(lx,v,u,w);
	edges.push_back(e1);
	points[u].push_back(edges.size()-1);
	edges.push_back(e2);
	points[v].push_back(edges.size()-1);
}

long long dijkstra()
{
	heapnode np(1,0);
	h.push(np);
	pre_length[1]=0;
	path_length[1]=0;
	while(!h.empty()) 
	{
		heapnode tp=h.top();
		h.pop();
		int p_index=tp.index;
		if(vis[p_index]==1) continue;
		vis[p_index]=1;
		if(p_index==n) return path_length[n];
		for(int i=0;i<points[p_index].size();i++)
		{
			edge te=edges[points[p_index][i]];
			long long t_path;
			if(te.index==0) 
			{
				t_path=te.weight+path_length[p_index];
			}
			else 
			{
				t_path=path_length[p_index]-(pre_length[p_index]*pre_length[p_index])+(pre_length[p_index]+te.weight)*(pre_length[p_index]+te.weight);
			}
			if(t_path<path_length[te.to])
			{
				path_length[te.to]=t_path;
				if(te.index==0) pre_length[te.to]=0;
				else pre_length[te.to]=pre_length[p_index]+te.weight;
				heapnode ad(te.to,path_length[te.to]);
				h.push(ad);
			}
		}
	}
}

int main()
{
	init();
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int a,b,c,d;
		scanf("%d %d %d %d",&a,&b,&c,&d);
		addedge(a,b,c,d);
	}
	printf("%lld",dijkstra());
	return 0;
}

心得体会

写完之后我发现,其实还是有一些问题的,在一些特殊的情况下很可能出错:就比如,下面这个图:
3 3
1 1 2 1
0 1 2 1
1 2 3 1
就是说,1和2之间是由一条小路和一条大路连接的,,2和3之间是只有一条小路。此时,因为权重为1,从1出发走到2无论走大路还是小路疲劳值都是1,但是如果算法中认为走小路为最优,那么从2走到3的疲劳值最终为4,而不为理论值2。但是好像数据点没有这种情况。。但是为了准确,应该在dijkstra的松弛时额外判断相等的情况,在这种情况下,优先走大路,即可避免,修改后的djikstra算法如下:

long long dijkstra()
{
	heapnode np(1,0);
	h.push(np);
	pre_length[1]=0;
	path_length[1]=0;
	while(!h.empty()) 
	{
		heapnode tp=h.top();
		h.pop();
		int p_index=tp.index;
		if(vis[p_index]==1) continue;
		vis[p_index]=1;
		if(p_index==n) return path_length[n];
		for(int i=0;i<points[p_index].size();i++)
		{
			edge te=edges[points[p_index][i]];
			long long t_path;
			if(te.index==0) 
			{
				t_path=te.weight+path_length[p_index];
			}
			else 
			{
				t_path=path_length[p_index]-(pre_length[p_index]*pre_length[p_index])+(pre_length[p_index]+te.weight)*(pre_length[p_index]+te.weight);
			}
			if(t_path<path_length[te.to])
			{
				path_length[te.to]=t_path;
				if(te.index==0) pre_length[te.to]=0;
				else pre_length[te.to]=pre_length[p_index]+te.weight;
				heapnode ad(te.to,path_length[te.to]);
				h.push(ad);
			}
			else if(t_path==path_length[te.to])
			{
				if(te.index==0) pre_length[te.to]=0;
			}
		}
	}
}

经检验也通过了测试点。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值