山东理工大学2021年全国天梯赛赛前个人专题强化赛---4(最短路)补题

8 篇文章 1 订阅

拯救007(升级版)

在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑袋跳上岸去!(据说当年替身演员被最后一条鳄鱼咬住了脚,幸好穿的是特别加厚的靴子才逃过一劫。)

设鳄鱼池是长宽为100米的方形,中心坐标为 (0, 0),且东北角坐标为 (50, 50)。池心岛是以 (0, 0) 为圆心、直径15米的圆。给定池中分布的鳄鱼的坐标、以及007一次能跳跃的最大距离,你需要给他指一条最短的逃生路径 —— 所谓“最短”是指007要跳跃的步数最少。

输入格式:
首先第一行给出两个正整数:鳄鱼数量 N(≤100)和007一次能跳跃的最大距离 D。随后 N 行,每行给出一条鳄鱼的 (x,y) 坐标。注意:不会有两条鳄鱼待在同一个点上。

输出格式:
如果007有可能逃脱,首先在第一行输出007需要跳跃的最少步数,然后从第二行起,每行给出从池心岛到岸边每一步要跳到的鳄鱼的坐标 (x,y)。如果没可能逃脱,就在第一行输出 0 作为跳跃步数。如果最短路径不唯一,则输出第一跳最近的那个解,题目保证这样的解是唯一的。

输入样例 1:
17 15
10 -21
10 21
-40 10
30 -50
20 40
35 10
0 -10
-25 22
40 -40
-30 30
-10 22
0 11
25 21
25 10
10 10
10 35
-30 10
输出样例 1:
4
0 11
10 21
10 35
输入样例 2:
4 13
-12 12
12 12
-12 -12
12 -12
输出样例 2:
0

思路:
一.把能跳互相之间能跳的点,通过数组连接起来,形成一副图,图内要记录谁跳到谁,还有距离。
.对于每个能跳的点都要以距离进行排序,因为题目说了如果有步数相同的,就以第一个跳的点的距离小的来输出。
.bfs的操作,以第一个点(原点)开始,检索能否可以直接逃脱。
1.如果不可以直接逃脱,就检索这个点,都可以跳到哪些点,都放进队列中,并且标记已经跳过了。要用path[]数组记录,谁跳到谁,也就是路径。比如path[2]=1,就定义为可以从1跳到2,即下标是跳的起点,值是跳的终点。
2.如果检索到了某个点可以直接逃脱,就开始输出步数和路径。步数的话,就在存路径的时候,不停的+1即可,而路径就需要开一个新的ans[]数组,来存点的编号了,要从外往里存,这是因为当初用path[]连接的两个点时候,数组的值是上一个点,所以从外往里存,是方便遍历的,每次遍历完一个点,就更新path[]数组,就可以知道它的上一个点的编号是谁了。ans[]数组里存的是,点的下标,最后输出的时候倒着输出就可以。

下面是把能跳过去的点之间连接起来,形成一幅图

for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){ //这里是把能跳过去的点连接了起来,形成了图,能跳过去说明是有边的
            double di=dis(i,j);
            if(i==1){     //初始点特判
                if(di<=d+7.5){ 
                p[i].push_back(make_pair(j,di)); //这里pair存的是i-j距离为di,表示这两个点可连接
                p[j].push_back(make_pair(i,di));
                }
            }
            else{
                if(di<=d){ //其余点只要能跳过去,就连起来,
                    p[i].push_back(make_pair(j,di));
                    p[j].push_back(make_pair(i,di));
                }
            }
        }
    }

这里是根据距离进行排序

for(int i=1;i<=n;i++){ //根据边的长度来进行排序
        sort(p[i].begin(),p[i].end(),cmp);
    }

这里是点的检索过程,包含了2小步。1是检索了某个点可以逃脱,2是没检索到,那就检索下一个点,并用队列把点的编号才能下来。

void bfs(){
    vis[1]=1; //第一个点已经走过去了,所以标记
    queue<int> q;
    q.push(1);  //把原点放进去
    while(!q.empty()){
        int k=q.front();
        q.pop();
        if(check(k)){ //如果某个点可以直接跳出岛,就开始输出
            int ans[N],cnt=0;  //ans存的是跳的点的编号,cnt是跳的数量
            while(path[k]!=0){ //从外往里,存入点,当path[k]为0时,说明是原点了
                ans[cnt++]=k;  //把第k号点存入ans
                k=path[k];   //更新k为,第k号点的前一个点,因为path[k]存的就是谁跳到k,值是跳的起始点、下标就是跳的终点
            }
            printf("%d\n",cnt+1); //因为cnt没记录从最后一个鳄鱼点,跳到岛外,所以要+1
            for(int i=cnt-1;i>=0;i--) printf("%.0lf %.0lf\n",x[ans[i]],y[ans[i]]); //这里因为存的时候就是倒着存的,所以要倒着输出
            return ;
        }
        for(int i=0;i<p[k].size();i++){  
            int now=p[k][i].first; //指的是点的编号,
            if(!vis[now]){  //没走过的话,就走
                vis[now]=1;
                path[now]=k; //path数组的意思为可以从k跳到now,也就是跳到nowd点的上一个点的编号
                q.push(now); //把这个点放进队列中
            }
        }
    }
    printf("0"); //最后一个路都没检索出来,那就是0
}

下面是完整代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
double x[N],y[N],vis[N],d; //分别是x,y坐标,是否走过的状态,和跳的距离
int n,path[N]; //记录边,就是谁能跳到谁
vector<pair<double,double>>p[N];  //以距离排序
bool cmp(pair<double,double> a,pair<double,double> b){
    return a.second<b.second;
}
double dis(int a,int b){  //求两点之间的距离
    return sqrt(pow(x[a]-x[b],2)+pow(y[a]-y[b],2));
}
bool check(int a){  //判断能否逃脱
    if(50-abs(x[a])<=d||50-abs(y[a])<=d) return 1;
    return 0;
}
void bfs(){
    vis[1]=1; 
    queue<int> q;
    q.push(1);  
    while(!q.empty()){
        int k=q.front();
        q.pop();
        if(check(k)){ 
            int ans[N],cnt=0;  
            while(path[k]!=0){ 
                ans[cnt++]=k;  
                k=path[k];   
            }
            printf("%d\n",cnt+1); 
            for(int i=cnt-1;i>=0;i--) printf("%.0lf %.0lf\n",x[ans[i]],y[ans[i]]); 
            return ;
        }
        for(int i=0;i<p[k].size();i++){  
            int now=p[k][i].first; 
            if(!vis[now]){  
                vis[now]=1;
                path[now]=k; 
                q.push(now); 
            }
        }
    }
    printf("0"); 
}
int main(){
    scanf("%d%lf",&n,&d);
    ++n;
    x[1]=0,y[1]=0;
    for(int i=2;i<=n;i++){
        scanf("%lf%lf",&x[i],&y[i]); 
    }
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){ 
            double di=dis(i,j);
            if(i==1){  
                if(di<=d+7.5){ 
                p[i].push_back(make_pair(j,di)); 
                p[j].push_back(make_pair(i,di));
                }
            }
            else{
                if(di<=d){ 
                    p[i].push_back(make_pair(j,di));
                    p[j].push_back(make_pair(i,di));
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        sort(p[i].begin(),p[i].end(),cmp);
    }
    bfs();
    return 0;
}

Harry Potter’s Exam

In Professor McGonagall’s class of Transfiguration, Harry Potter is learning how to transform one object into another by some spells. He has learnt that, to turn a cat into a mouse one can say docamo! To reverse the effect, simply say decamo! Formally speaking, the transfiguration spell to transform between object A and object B is said to be S if there are two spells, doS and deS, to turn A into B and vice versa, respectively.

In some cases, short-cut spells are defined to make transfiguration easier. For example, suppose that the spell to transform a cat to a mouse is docamo, and that to transform a mouse into a fatmouse is dofamo, then to turn a cat into a fatmouse one may say docamodofamo! Or if a shot-cut spell is defined to be cafam, one may get the same effect by saying docafam!

Time is passing by quickly and the Final Exam is coming. By the end of the transfiguration exam, students will be requested to show Professor McGonagall several objects transformed from the initial objects they bring to the classroom. Each of them is allowed to bring 1 object only.

Now Harry is coming to you for help: he needs a program to select the object he must take to the exam, so that the maximum length of any spell he has to say will be minimized. For example, if cat, mouse, and fatmouse are the only three objects involved in the exam, then mouse is the one that Harry should take, since it will take a 6-letter spell to turn a mouse into either a cat or a fatmouse. Cat is not a good choice since it will take at least a 7-letter spell to turn it into a fatmouse. And for the same reason Harry must not take a fatmouse.

Input Specification:
Each input file contains one test case. For each case, the first line contains two positive integers N (≤100) and M, which are the total number of objects involved in the exam and the number of spells to be tested, respectively. For the sake of simplicity, the objects are numbered from 1 to N. Then M lines follow, each contains 3 integers, separated by a space: the numbers of two objects, and the length of the spell to transform between them.

Output Specification:
For each test case, print in one line the number of the object which Harry must take to the exam, and the maximum length of the spell he may have to say. The numbers must be separated by a space.

If it is impossible to complete all the transfigurations by taking one object only, simply output 0. If the solution is not unique, output the one with the smallest number.

Sample Input:
6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
Sample Output:
4 70
题意: 就是让你找一个点,这个点到其他点的最短路径的最大值,比其他点的最短路径的最大值都要小,问你这个点是那个点,并输出这个点到其他店的最短路的最大值。
思路: 直接floyd,对每个点都求它的最短路的最大值,当遇见更小的时候,就更新点和最短路的最大值。

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N=1e2+10;
int n,m;
int p[N][N];
void floyd(){
     for(int k=1;k<=n;k++){
         for(int i=1;i<=n;i++){
             for(int j=1;j<=n;j++){
                 if(p[i][j]>p[i][k]+p[k][j]){
                     p[i][j]=p[i][k]+p[k][j];               
                 }
             }
         }
     }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(p,inf,sizeof(p));
    for(int i=0;i<m;i++){
        int u,v,dis;
        scanf("%d%d%d",&u,&v,&dis);
        p[u][v]=p[v][u]=dis; //无向图
    } 
    floyd(); //多源最短路
    int minn=inf,now=-1; //分别用来求最短路的最大值,和点的编号
    for(int i=1;i<=n;i++){
         int maxx=0,flag=1;
         for(int j=1;j<=n;j++){
             if(i==j){  //自己到自己不考虑
                 continue;
             }
            if(p[i][j]==inf){ //如果不能过去,说明这个点不能到其他所有的点
                flag=0;
                 break;
             }
             if(p[i][j]>maxx){ //更新最短路的最大值
                 maxx=p[i][j];
             }
         }
         if(flag==0) continue;
         else{
             if(maxx!=0&&maxx<minn){ //如果找到了更小的最短路的最大值,就更新
                 minn=maxx;
                 now=i;
             }
         }
    }
    if(minn==inf) printf("0\n");
    else{
        printf("%d %d\n",now,minn);
    }
    return 0;
}

城市间紧急救援

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:
输入第一行给出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
思路: 用迪杰斯特拉来找最短路,每当找到一个最短路是,就要跟新距离,更新最短路径条数,途中召集的救援队人数。
用num[]数组记录每个点本来有的救援队数量
用p[]数组记录途中召集的救援队数量。
用path[]数组记录到该点的上一个点是那个点,也就是谁到了这个点。
用q[]数组记录最短路径条数。
每当找到最短路径的时候,距离就更新为短的那个。
最短路径条数就更新为前一个点的最短路径条数,(举例)比如从a到b,因为是从a点通过最短路来到b点的,想要到b(最短距离情况下 ,其他距离下就不叫最短路径条数了)就要经过a,所以a有几条,b就有几条。
召集的救援队数量就变成你在途经b点之前召集的人+b点当地的人数
当遇见最短路经一样时,就要把这两种情况的最短路径条数加起来,因为这两种都是最短路啊,此时就要对召集的救援队数量进行决策,肯定是选能着急更多的救援队数量的那条路。
每次更新时,都要记录到该点的上一点是那个点,方便最后输出。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
#define inf 0x3f3f3f3f
int n,m,s,d;
int g[N][N],num[N];//城市地图、该点救援队数量
int p[N],path[N],q[N]; //到该点时召集的救援队数量、到达该城市前所经历的城市编号、到该点的最短路径条数
int vis[N]; //是否走过
void dijkstra(){
    vis[s]=1; //起点走过了标记为1
    q[s]=1; 
    for(int i=0;i<n;i++){
        int min=inf,k=-1;
        for(int j=0;j<n;j++){  //这里是找最短路
            if(vis[j]==0&&g[s][j]<min){
                 min=g[s][j]; //记下最短路
                 k=j;     //记下编号
            }
        }
        if(k==-1) break; 
        else vis[k]=1; //走到该点标记为1
        for(int j=0;j<n;j++){ 
            if(vis[j]==0&&g[s][j]>g[s][k]+g[k][j]){
                g[s][j]=g[s][k]+g[k][j]; //更新从起点到j的最短距离
                q[j]=q[k];    //因为是从到k走到j,所以到k有几条最短路径那么到j就有几条,因为要到j,就要经过k.
                p[j]=p[k]+num[j]; //到j点召集的救援队数量就是在k点召集的数量+当地的数量
                path[j]=k; //path存的是可以从k点到j点,也就是j点的上一点的编号
            }
            else if(vis[j]==0&&g[s][j]==g[s][k]+g[k][j]){ //当有最短路径一样的时候
                q[j]=q[j]+q[k]; //到j点的最短路径就要更新了,因为此时到j点有有两种途径而且最短路都一样,那就把他们的最短路加起来
                if(p[j]<p[k]+num[j]){ //最短路相同时,就要比较救援队数量了,更新多的那个
                    path[j]=k; 
                    p[j]=p[k]+num[j];
                }
            }
        }
    }
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&d);//城市数、边数、出发、目的地
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(i!=j){
               g[i][j]=inf;
            }
        }
    }
    for(int i=0;i<n;i++){
        scanf("%d",&num[i]); 
        p[i]=num[i];  
        q[i]=1; //到每个点的最短路径条数先都记为1
    }
    for(int i=0;i<m;i++){
        int u,v,dis;
        scanf("%d%d%d",&u,&v,&dis);
        g[u][v]=g[v][u]=dis;
    }
    dijkstra();
    printf("%d %d\n",q[d],p[d]+num[s]); //最短路径条数、原点救援队数量+途中召集的救援队数量
    int road[N]; //来存路径的
    int cnt=0,t=d;
    while(path[t]!=0){  //path存的是跳到该点的上一个点是什么,所以要从后往前存
        road[cnt++]=path[t]; 
        t=path[t];
    }
    printf("%d",s);
    for(int i=cnt-1;i>=0;i--){
        printf(" %d",road[i]);
    }
    printf(" %d\n",d);
    return 0;
}

To be continued
如果你有任何建议或者批评和补充,请留言指出,不胜感激

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值