1087 All Roads Lead to Rome (30分) Dijkstra维护最大价值最少节点的最短路,最短路的条数 Apare_xzc

3 篇文章 0 订阅
2 篇文章 0 订阅

1087 All Roads Lead to Rome (30分) Dijkstra Apare_xzc


题目链接:1087 All Roads Lead to Rome (30分)


题面:

在这里插入图片描述

题意:

        你是一个导游,要让你的顾客从当前城市出发,到达“ROM”这个目的地,图中可以游览别的城市。每个城市都有一个可以使旅客快乐的值。从一个城市到另一个城市要花费一定的路费。你要求出从起点到终点的旅客花费最少的路径。如果有多条路径,那么选择沿途快乐值之和最大的路线。如果仍有多条路线,那么选择图中经过城市个数最少的路线(我的翻译)。
        数据保证有且仅由一条路线。一共有n个城市,城市之间有k条双向道路。每个城市都有一个名字,用三个字母的字符串表示。


输入格式:

        第1行,n,k,st 表示城市个数,道路个数,以及起点城市的名字。
        第2行到第n行, 这n-1行是除了起点之外的其它城市的信息。每行一个字符串和一个整数,代表城市的名字和快乐值。
        下面的m行,每行都有两个字符串u,v和一个整数d,代表城市u,v之间有一条花费为d的道路。


输出格式:

        输出有2行,第一行是用空格隔开的4个整数,分别表示最少花费道路的数目,最少的花费值,最大的快乐值之和,最大的平均快乐值(保留整数) 第二行用st->p1->p2…->ROM的格式输出所求的路径,具体格式参见样例。


样例输入:

6 7 HZH
ROM 100
PKN 40
GDN 55
PRS 95
BLN 80
ROM GDN 1
BLN ROM 1
HZH PKN 1
PRS ROM 2
BLN HZH 2
PKN GDN 1
HZH PRS 1

样例输出:

3 3 195 97
HZH->PRS->ROM

AC


分析:

        根据题意,要求最短路,由于是没有负权边的无向图,我们肯定用Dijkstra算法。这道题,不但要求最短路的大小(最小花费),还要求阻断路的条数。以及输出一条最短的,快乐值之和最大的,节点最少的道路。这其实都是更新节点时候几个if-else的事情,并不难。
        首先我们说结点的表示。由于输入的是字符串代表城市的名字,我们要把每个城市名字变成编号方便我们计算。可以建立string和int之间的映射,用map维护最为方便。
        再说说存图。由于这个题结点个数n<=200,所以用邻接表或者邻接矩阵皆可以。我更倾向于使用数组模拟的邻接表(链式前向星)
        还有Dijkstra的写法,可以不用堆优化,因为数据不大,O(n^2)的复杂度可以接受。但使用优先队列优化可以使时间复杂度为Klog2K,对于稀疏图更加快。
        关于维护最短路,我们知道Dijkstra其实是一种贪心算法,可以求出起点到其它节点的最短路径(单源最短路)。从起点开始,起点到自己的距离为0。我们循环n-1次,每次找出还没有求出最短路的剩余的节点中距离起点最近的一个节点u,于是这个距离一定是u到起点的最短距离。再用u去更新所有与u相邻的节点v到起点的最短距离。Dijkstra算法求最短路的原理同Kruskal算法求最小生成树。之所以要设置标记数组vis, 是因为之前已经求得最短路的结点已经得到最优值,即使再更新,对于答案也没有改变。是多余的计算。(扯远了)
        关于求最短路径的条数,我们并不需要dfs搜索。只需要在“用当前选出距离起点最近的节点u来更新和u相邻的节点v”的时候,顺便维护一个cntRoute[]数组,表示从起点到该节点,满足路径长度最短(花费最小)的路径的条数。如果点u和v之间有一条边,cnt[u]已知,那么就能用cnt[u]来更新cnt[v]

  • 在用u更新v的时候,如果dis[u] + cost[u][v] < dis[v], 那么当前原点到v的最短路一定只能先去u,再去v ,那么 cnt[v] = cnt[u]
  • 如果dis[u] + cost[u][v] == dis[v] 那么,原点可以先去别的点,再去v, 亦可以先去u,再到v, 在当前这个最短路径 dis[v] 之下, 路径的条数又多了 几条原点先去u在去v的路径,此时 cnt[v] += cnt[u]

        关于维护最大的快乐值,只需要在用u更新v的时候,如果花费相同,就选择快乐值大的路径,如果快乐值一样大,那么就选择路径上结点个数少的路径(因为要求城市平均快乐值最大)
        关于记录路径,我们不需要每个结点都记录全部的路径,只需要记录每个结点的前驱节点即可。这样的话只需要循环一遍,每次找前驱,直到从终点找到起点,记录到栈或数组里面,逆序输出即可,或者路径不长的话可以递归。


我的AC代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 200+10;
const int maxm = 40000+10;
map<string,int> mp; //string->int 城市名到编号的映射 
LL val[maxn]; //每个城市的快乐值 
int n,m,cnt=1; //n是城市个数,m是道路数目,cnt用于给城市分配编号 
string st,Name[maxn];
//st是起点的城市名,Name[]数组记录城市编号对应的名字 
struct Node{ 
	int to,Next;
	LL d;
}node[maxm];//维护链式前向星(数组模拟邻接表) 
int head[maxn],tot; //头结点,节点总数 
void initEdge()
{ //初始化邻接表 
	tot = 0;
	memset(head,-1,sizeof(head));	
} 
void addedge(int u,int v,LL d)
{ //加边 
	node[tot].to = v;
	node[tot].d = d;
	node[tot].Next = head[u];
	head[u] = tot++;	
} 
struct P{ //Dijkstra()中优先队列存储的数据结构 
	int id; //结点编号 
	LL dis; //结点当前距离起点的最短距离 
	bool operator < (const P & rhs)const{
		return dis > rhs.dis;
	} //重载小于号 
	P(){} //无参构造函数 
	P(int _id,LL _dis)
	{ //构造函数 
		id = _id;
		dis = _dis; 
	} 
//	friend ostream& operator << (ostream& out,const P& p)
//	{
//		out<<"id = "<<p.id<<", dis = "<<p.dis;
//		return out;
//	}
}p;
int Pre[maxn]; //记录前驱节点 
bool vis[maxn]; //标记数组 
LL sum[maxn]; //记录最大快乐值之和 
LL dis[maxn]; //记录最短距离 
int Num[maxn]; //记录最短路径上的结点个数 
LL cntRoute[maxn]; //记录最短路的条数 
void Dijkstra()
{
	priority_queue<P> Q;
	memset(vis,false,sizeof(vis));
	memset(dis,0x3f,sizeof(dis)); //初始化 
	dis[1] = 0; //起点的编号为1 
	sum[1] = val[1];
	cntRoute[1] = 1;
	Q.push(P(1,0)); //起点入队 
	while(!Q.empty())
	{
		p = Q.top();Q.pop();
		if(vis[p.id]) continue; 
		vis[p.id] = true; //标记 
		for(int i=head[p.id];i!=-1;i=node[i].Next)
		{  //用u更新v 
			int to = node[i].to;
			if(vis[to]) continue;
			LL d = node[i].d;
			if(dis[p.id]+d<dis[to]) //距离更短,直接更新 
			{
				dis[to] = dis[p.id]+d; 
				Pre[to] = p.id;
				sum[to] = sum[p.id]+val[to];
				Num[to] = Num[p.id]+1;
				cntRoute[to] = cntRoute[p.id];
				Q.push(P(to,dis[to]));
			}	
			else if(dis[p.id]+d==dis[to]) //距离相同 
			{
				cntRoute[to] += cntRoute[p.id];
				if(sum[p.id]>sum[Pre[to]])
				{
					Pre[to] = p.id;
					sum[to] = sum[p.id]+val[to];
					Num[to] = Num[p.id]+1;
					Q.push(P(to,dis[to]));
				}
				else if(sum[p.id]==sum[Pre[to]]&&Num[p.id]+1<Num[to])
				{
					Pre[to] = p.id;
					Num[to] = Num[p.id]+1;
					Q.push(P(to,dis[to]));
				}
			}
		}		
	}
	int ed = mp[string("ROM")];
	printf("%lld %lld %lld %lld\n",cntRoute[ed],dis[ed],sum[ed],sum[ed]/Num[ed]);
	stack<int> Sta;
	while(ed!=1) //得出路径 
	{
		Sta.push(ed);
		ed = Pre[ed];
	}
	cout<<Name[1];
	while(!Sta.empty())
	{
		cout<<"->"<<Name[Sta.top()];
		Sta.pop();	
	}	
	puts("");
}
int main()
{
//	freopen("in.txt","r",stdin);
	cin>>n>>m>>st;
	string u,v;
	LL d;
	mp[st] = 1; //起点为1 
	Name[1] = st;
	for(int i=1;i<n;++i)
	{
		cin>>u>>d;
		if(mp.count(u)) val[mp[u]] = d;
		else 
		{ //给城市分配编号 
			mp[u] = ++cnt; val[cnt] = d;
			Name[cnt] = u;
		}
	}
	int iu,iv;
	initEdge();
	for(int i=1;i<=m;++i)
	{
		cin>>u>>v>>d;
		iu = mp[u], iv = mp[v];
		addedge(iu,iv,d);
		addedge(iv,iu,d);
	} 
	memset(Pre,0,sizeof(Pre));
	Dijkstra();
	
	return 0;
}

AC愉快~
xzc
2020.2.5 22:26


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值