在上一篇中我们进行了图的专项练习,在这一篇中我们重点探讨图的编程专项习题。
目录
编程题
R7-1 城市间紧急救援
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
输入格式:
输入第一行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。
第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。
输出格式:
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
输出样例:
2 60
0 1 3
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int map[1010][1010],a[1010],b[1010]; // 定义邻接矩阵map、每个顶点的权值a、到每个顶点的最小权值和b
int dis[1010],vis[1010],l[1010],sum[1010]; // 定义源点到各顶点的距离dis,标记数组vis,前驱数组l,到达某个顶点的最短路径条数sum
int n,m,d,s; // 定义顶点数n、边数m、起点s和终点d
void dijstra(int x) // Dijkstra算法求解最短路径
{
int i,j,k,minn;
b[x]=a[x]; // 初始化起点的最小权值和为a[s]
for(i=0;i<n;i++)
{
dis[i]=map[x][i]; // 初始化源点到各顶点的距离为起点到i的边权值
l[i]=-1; // 初始化前驱数组l为-1,表示没有前驱
if(dis[i]<inf) // 如果存在从起点s到顶点i的边
{
b[i]=a[x]+a[i]; // 更新到顶点i的最小权值和为a[s]+a[i]
sum[i]=1; // 到达顶点i的最短路径条数初始化为1
l[i]=x; // 更新前驱为起点s
}
}
l[x]=-1; // 起点s没有前驱
vis[x]=1; // 标记起点s已经访问过
for(i=0;i<n;i++)
{
minn=inf;
for(j=0;j<n;j++)
{
if(!vis[j]&&dis[j]<minn) // 找到未访问过的距离源点最近的顶点
{
minn=dis[j];
k=j;
}
}
vis[k]=1; // 标记k顶点已经访问过
for(j=0;j<n;j++)
{
if(!vis[j]&&dis[j]>minn+map[k][j]) // 如果通过k顶点可以更新j顶点的最短路径
{
dis[j]=minn+map[k][j]; // 更新源点到j顶点的距离
b[j]=b[k]+a[j]; // 更新到j顶点的最小权值和
l[j]=k; // 更新j顶点的前驱为k顶点
sum[j]=sum[k]; // 更新到达j顶点的最短路径条数
}
else if(!vis[j]&&dis[j]==minn+map[k][j]) // 如果通过k顶点可以到达j顶点的最短路径有多条
{
sum[j]=sum[j]+sum[k]; // 更新到达j顶点的最短路径条数
if(b[j]<b[k]+a[j]) // 如果更新后到达j顶点的最小权值和更小,则更新前驱为k顶点
{
b[j]=b[k]+a[j];
l[j]=k;
}
}
}
}
}
void find(int x) // 递归输出路径上的所有顶点
{
if(l[x]!=-1)
{
find(l[x]);
printf("%d ",l[x]);
}
}
int main()
{
memset(map,inf,sizeof(map));
memset(vis,0,sizeof(vis));
int i,j,x,y,t;
scanf("%d%d%d%d",&n,&m,&s,&d);
for(i=0;i<n;i++)
scanf("%d",&a[i]); // 输入每个顶点的权值
for(i=0;i<m;i++)
{
scanf("%d%d%d",&x,&y,&t);
map[x][y]=map[y][x]=t; // 输入每条边的起点、终点和边权,构造邻接矩阵
}
dijstra(s); // 求解从起点s到各顶点的最短路径
printf("%d %d\n",sum[d],b[d]); // 输出从起点s到终点d的最短路径条数和最小权值和
find(d); // 输出从起点s到终点d的最短路径上的所有顶点
printf("%d\n",d);
return 0;
}
R7-2 地铁一日游
森森喜欢坐地铁。这个假期,他终于来到了传说中的地铁之城——魔都,打算好好过一把坐地铁的瘾!
魔都地铁的计价规则是:起步价 2 元,出发站与到达站的最短距离(即计费距离)每 K 公里增加 1 元车费。
例如取 K = 10,动安寺站离魔都绿桥站为 40 公里,则车费为 2 + 4 = 6 元。
为了获得最大的满足感,森森决定用以下的方式坐地铁:在某一站上车(不妨设为地铁站 A),则对于所有车费相同的到达站,森森只会在计费距离最远的站或线路末端站点出站,然后用森森美图 App 在站点外拍一张认证照,再按同样的方式前往下一个站点。
坐着坐着,森森突然好奇起来:在给定出发站的情况下(在出发时森森也会拍一张照),他的整个旅程中能够留下哪些站点的认证照?
地铁是铁路运输的一种形式,指在地下运行为主的城市轨道交通系统。一般来说,地铁由若干个站点组成,并有多条不同的线路双向行驶,可类比公交车,当两条或更多条线路经过同一个站点时,可进行换乘,更换自己所乘坐的线路。举例来说,魔都 1 号线和 2 号线都经过人民广场站,则乘坐 1 号线到达人民广场时就可以换乘到 2 号线前往 2 号线的各个站点。换乘不需出站(也拍不到认证照),因此森森乘坐地铁时换乘不受限制。
输入格式:
输入第一行是三个正整数 N、M 和 K,表示魔都地铁有 N 个车站 (1 ≤ N ≤ 200),M 条线路 (1 ≤ M ≤ 1500),最短距离每超过 K 公里 (1 ≤ K ≤ 106),加 1 元车费。
接下来 M 行,每行由以下格式组成:
<站点1><空格><距离><空格><站点2><空格><距离><空格><站点3> … <站点X-1><空格><距离><空格><站点X>
其中站点是一个 1 到 N 的编号;两个站点编号之间的距离指两个站在该线路上的距离。两站之间距离是一个不大于 106 的正整数。一条线路上的站点互不相同。
注意:两个站之间可能有多条直接连接的线路,且距离不一定相等。
再接下来有一个正整数 Q (1 ≤ Q ≤ 200),表示森森尝试从 Q 个站点出发。
最后有 Q 行,每行一个正整数 Xi,表示森森尝试从编号为 Xi 的站点出发。
输出格式:
对于森森每个尝试的站点,输出一行若干个整数,表示能够到达的站点编号。站点编号从小到大排序。
输入样例:
6 2 6
1 6 2 4 3 1 4
5 6 2 6 6
4
2
3
4
5
输出样例:
1 2 4 5 6
1 2 3 4 5 6
1 2 4 5 6
1 2 4 5 6
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <map>
#include <vector>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n, m, k, dp[205][205]; // 定义顶点数n,边数m,每条边的费用k,和表示最短路径的二维数组dp
vector<int> g[205]; // 保存最终关系网
bool vis[205]; // 标记数组,用于DFS访问
vector<int> res; // 最终能经过的点
bool flag[205]; // 记录是否为末端站点
void dfs(int now){
// 深度优先搜索遍历图
for(int i = 0; i < g[now].size(); i++){
int to = g[now][i];
if(!vis[to]){
vis[to] = true;
res.push_back(to);
dfs(to);
}
}
}
signed main()
{
cin >> n >> m >> k; // 输入顶点数、边数和费用
memset(dp, 0x3f, sizeof dp); // 初始化最短路径数组为无穷大
for(int i = 1; i <= m; i+