C++/PTA 直捣黄龙

题目要求

本题是一部战争大片 —— 你需要从己方大本营出发,一路攻城略地杀到敌方大本营。首先时间就是生命,所以你必须选择合适的路径,以最快的速度占领敌方大本营。当这样的路径不唯一时,要求选择可以沿途解放最多城镇的路径。若这样的路径也不唯一,则选择可以有效杀伤最多敌军的路径。

输入格式:
输入第一行给出2个正整数N(2 ≤ N ≤ 200,城镇总数)和K(城镇间道路条数),以及己方大本营和敌方大本营的代号。随后N-1行,每行给出除了己方大本营外的一个城镇的代号和驻守的敌军数量,其间以空格分隔。再后面有K行,每行按格式城镇1 城镇2 距离给出两个城镇之间道路的长度。这里设每个城镇(包括双方大本营)的代号是由3个大写英文字母组成的字符串。

输出格式:
按照题目要求找到最合适的进攻路径(题目保证速度最快、解放最多、杀伤最强的路径是唯一的),并在第一行按照格式己方大本营->城镇1->…->敌方大本营输出。第二行顺序输出最快进攻路径的条数、最短进攻距离、歼敌总数,其间以1个空格分隔,行首尾不得有多余空格。

输入样例:
10 12 PAT DBY
DBY 100
PTA 20
PDS 90
PMS 40
TAP 50
ATP 200
LNN 80
LAO 30
LON 70
PAT PTA 10
PAT PMS 10
PAT ATP 20
PAT LNN 10
LNN LAO 10
LAO LON 10
LON DBY 10
PMS TAP 10
TAP DBY 10
DBY PDS 10
PDS PTA 10
DBY ATP 10

输出样例:
PAT->PTA->PDS->DBY
3 30 210


解题思路

  1. 定义一个结构体 edge 用于存储图中的一条边,包括该边所指向的结点和边的权值。

  2. 定义一个 e 的映射结构体 map<string,vector<edge>>e 来存储图,其中 e[u] 表示结点 u 连接的所有结点及其对应的边权值。

  3. 定义各种映射或集合来存储最短距离、是否访问、城镇人数、解放城镇数量、杀敌数和最短路径条数等信息。

  4. 使用 Dijkstra 算法求解最短路径,同时根据题目要求更新城镇人数、解放城镇数量、杀敌数和最短路径条数等信息。

  5. 获取路径时,从终点开始不断向前回溯,直到回溯到起点,将每个结点加入一个 vector 中,最后依次输出即可。

  6. 输出城镇人数、解放城镇数量、杀敌数和最短路径条数等信息,即 people[ed]city[ed]man[ed]shortpath[ed]


代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define endl "\n"
const int INF=2147483647;
int n,m,num;//城镇数 道路数 
string be,ed,s;//起点 终点 城镇 
struct edge{
	string v;
	int w;
};
map<string,vector<edge>>e;//存图
//最短距离 是否访问 城镇人数 解放城镇数量 杀敌数 最短路径条数  
map<string,int>d,vis,man,city,people,shortpath;
map<string,string>pre;//保存前驱 
unordered_set<string>st;//存有什么地点
 
priority_queue<pair<int,string>>q;
void dijkstra(string s){
	for(auto i:st) d[i]=INF;
	d[s]=0;q.push({0,s});
	shortpath[s]=1;
	while(q.size()){
		auto t=q.top();q.pop();
		string u=t.second;
		if(vis[u]) continue;
		vis[u]=1;
		for(auto ed:e[u])
		{
			string v=ed.v;
			int w=ed.w;
			if(d[v]>d[u]+w){//距离要最短 
				d[v]=d[u]+w;
				city[v]=city[u]+1;
				people[v]=people[u]+man[v];
				shortpath[v]=shortpath[u];
				q.push({-d[v],v});
				pre[v]=u;
			}else if(d[v]==d[u]+w){//最短路径不唯一时 
				shortpath[v]+=shortpath[u];
				if(city[v]<city[u]+1){//解放城镇要最多 
					city[v]=city[u]+1;
					people[v]=people[u]+man[v];
					pre[v]=u;
				}else if(city[v]==city[u]+1){
					if(people[v]<people[u]+man[v]){//杀敌要最多 
						people[v]=people[u]+man[v];
						pre[v]=u;
					}	
				}
			}
		}
	}
}
vector<string>path;
void getpath(string s)//获取最短路径 
{
	while(s!="000"){
		path.pb(s);
		s=pre[s];
	}
}
int main()
{
	ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>be>>ed;
    pre[be]="000";//将起点前驱定为000 
    st.insert(be);
    for(int i=1;i<=n-1;i++)
    {
    	cin>>s>>num;
    	st.insert(s);
    	man[s]=num;
	}
    for(int i=0;i<m;i++)
    {
    	string u,v;
    	int w;
    	cin>>u>>v>>w;
    	e[u].pb({v,w});
    	e[v].pb({u,w});
	}
	dijkstra(be);
	getpath(ed);
	for(int i=path.size()-1;i>=0;i--)
	{
		if(i!=path.size()-1) cout<<"->";
		cout<<path[i];
	}
	cout<<endl;
	cout<<shortpath[ed]<<" "<<d[ed]<<" "<<people[ed];
	return 0;
} 

注意:

  • 起点的前驱应赋为 “000”。
  • 对于最短路径长度相同的情况,优先选择解放城镇更多的路径,其次是杀敌数更多的路径。

Dijkstra算法

Dijkstra 算法是用于求解带权有向图或无向图的单源最短路径问题的一种贪心算法。

基本思想

其基本思想是从起点开始,逐步选择未被标记为已访问的结点中距离起点最近的结点,并将其标记为已访问过,然后更新与该结点相邻的未访问结点的最短路径长度。重复这个过程直到所有结点都被标记为已访问,或者找到终点。

实现步骤

具体实现步骤如下:

  1. 初始化 dist 数组,表示起点到各个结点的最短路径长度。其中起点到自身的距离为 0,其他结点的距离为无穷大。

  2. 初始化 visited 数组,表示每个结点是否已被访问过。起点已经访问过,其他结点为未访问。

  3. 重复下述步骤,直到所有结点都被访问过:

  • 从未访问的结点中选取距离起点最近的结点 u,并将其标记为已访问;
  • 对于 u 的所有邻接结点 v,如果还没有被访问过,那么计算通过 u 到达 v 的路径长度,即 dist[u] + weight(u, v),并与当前记录的最短路径 dist[v] 进行比较。如果新路径更短,则更新 dist[v],并将 v 的前驱结点设置为 u。
  1. 最终得到所有结点的最短路径长度和前驱结点,可以通过回溯前驱结点的方式获取到从起点到终点的最短路径。

需要注意的是,Dijkstra 算法要求图中的边权值必须为非负数。如果存在负权边,可以使用 Bellman-Ford 算法或 SPFA 算法求解最短路径。


总结

本文使用Dijkstra算法映射体结构实现对最短路径的求解,读者可躬身实践。
我是秋说,我们下次见。

posted @ 2023-05-20 14:47  秋说  阅读(55)  评论(0编辑  收藏  举报  来源