PAT-A1131 Subway Map 题解

PAT-A1131 Subway Map 题解

首先感谢柳神的博客,看了她的博客发现自己的问题。
柳神的博客

题目链接:

PATA1131

题目大意:

给你一张地铁地图,然后给你m和查询记录,每次查询,都要给出从S->T的最短路线。如果有相同的最短路线,那么就给出最后换成次数最小的那一个。

思路分析:

很多人看到这道题会想到用单源最短路的算法去解。但是这里有一些条件:

  1. 给出的站点编号不是顺序出现的(当然可以用离散化的方法来处理,所以这一点似乎也可以克服)
  2. 最后需要求解路径,并且这个路径并非传统意义上的路径。它需要包含结点所属线路的相关信息。
  3. 有多个解

以上的这些特征,印证了这道题不好用单源最短路的算法来解决。我们看到数据规模,线路数目不会超过100条,每条线路的结点数量也不超过100,那么这个数据规模最大也只有10000.加上线路是线性的,如果使用搜索的方式,大部分的结点都只是单方向搜索,不会产生指数级别的复杂度。
因此,我们这里使用的解决方法是DFS。

算法设计

我们把问题聚焦于两个重要方面:

  1. 找出从起点到终点的路径。
    这个是一个比较简单深度优先搜索,大部分代码都不难写。
    下面给出伪代码
//index指示当前结点
void DFS(int index,int cnt){
	if(condition){return;}//当达到递归边界的时候,就直接return
	visit[index] = true;//把当前结点设置为不可访问,减少不必要的计算。
	path.add(index);
	for( ; ; ){
		DFS(nextIndex,cnt+1);	
	}
	visit[index] = false;
	path.pop(index);//回溯
}

  1. 换乘问题。也就是怎么在路径上发现换乘的结点
    这里有两个思路
    第一种,以结点为研究对象。记录每一个结点归属的线路编号。
    所以可以直接建立一个struct,里面记录了节点的编号,邻居结点,以及所属线路。但是有一个问题难以解决:那就是每个结点可能属于多个线路。假设从一个交叉路口的点出发,有多种线路选择,这样我们要遍历所有的线路,然后把对应的线路加到线路列表中,这无疑会加大问题的复杂性。
    第二种,以线路为研究对象。因为两个点之间的线路是唯一的,所以可以开一个二维矩阵line [ 10000 ] [ 10000 ].矩阵的值就是线路编号。但如果我们这么做了就直接MLE。那怎么办呢?因为这里的结点编号不会超过10000,我们就可以利用离散化和HashTable的思想,用一个hash函数来唯一的表示一个结点对。比如,10000*a+b就可以唯一的标识a、b节点对。再建立一个unordered_map来存储这个hash值对应的line,这样就解决了空间不够的这个问题。
    那么怎么发现换乘的现象呢?很简单,维护一个preLine,也就是之前所在的线路。如果现在的结点和下一个结点对应的line和这个preLine不一样,就表示要换乘了。我们把之前的线路打印出来,顺便更新这个preLine为现在的line。

好了,大部分的问题都解决了,剩下的就是写代码的事情了。

AC代码:

#include<unordered_map>
#include<iostream>
#include<cstdio>
#include<vector>
#define INF 0x3f3f3f3f
using namespace std;
const int MAX_N = 10010;
vector<int> mp[MAX_N];//实际上和vector<vector<int>> v(MAX_N);是等效的。
vector<int> tempPath,res;//分别记录了临时的路径以及最终的结果.
//用于记录两个相邻结点之间的路线是什么。左边的int其实是个hash函数。利用10000*a+b就可以唯一的标识一个四位数对。
unordered_map<int,int> line;
bool visit[MAX_N];
int S,T,minLength,minTrans;//minLength表示最短的路径、minTrans表示最小的换成次数。
//用于计数一个路径的换乘次数。
int transferCnt(const vector<int> &v){
    int ans = -1;
    int preLine = 0;
    for(int i = 0;i<v.size()-1;i++){
        if(line[v[i]*10000+v[i+1]] != preLine){
            ans++;
        }
        preLine = line[v[i]*10000+v[i+1]];
    }
    return ans;
}
/*
 * node表示当前的结点,length表示路径长度。
 */
void dfs(int node,int length){
    visit[node] = true;
    tempPath.push_back(node);
    if(node == T){
        if(length < minLength){
            res = tempPath;
            minLength = length;
        }
        else if(length == minLength)
        {
            if(transferCnt(tempPath) < transferCnt(res)){
                res = tempPath;
            }
        }
        visit[node] = false;
        tempPath.pop_back();
        return ;
    }
    for(int i = 0;i<mp[node].size();i++){
        if(!visit[mp[node][i]]){//如果周围的点没有被访问过
            dfs(mp[node][i],length+1);
        }
    }
    visit[node] = false;
    tempPath.pop_back();
}
//用于打印最后的运行路径。
void print_route(const vector<int> &r){
    int preNode,preLine,node,s,t;
    s = r[0];preNode = s;preLine = line[s*10000+r[1]];
    for(int i = 1;i<r.size();i++){
        if(line[preNode*10000+r[i]] != preLine){
            printf("Take Line#%d from %04d to %04d.\n",preLine,s,preNode);
            preLine = line[preNode*10000+r[i]];
            s = preNode;
        }
        preNode = r[i];
    }
    printf("Take Line#%d from %04d to %04d.\n",preLine,s,preNode);
}
void init(){//初始化函数.
    fill(visit,visit+MAX_N,false);//初始化visit数组.
    tempPath.clear();res.clear();minLength = INF;//初始化其他的变量
}

int main(){
    int n,m,k;
    int preNode,node;
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        scanf("%d%d",&k,&preNode);
        for(int j = 1;j<k;j++){
            scanf("%d",&node);
            //构建地图
            mp[node].push_back(preNode);mp[preNode].push_back(node);
            line[10000*preNode+node] = line[10000*node+preNode] = i;//用于记录路径
            preNode = node;
        }
    }
    scanf("%d",&m);
    for(int i = 0;i<m;i++){
        scanf("%d%d",&S,&T);//读取起点和终点.
        init();
        dfs(S,0);
        cout<<minLength<<endl;
        print_route(res);
    }
    return 0;
}

其实柳神的代码有些地方写的风格不是特别好。比如她的dfs函数,其实visit数组的修改完全可以放到这个函数体内部来完成,不需要在main函数中进行操作,这样反倒有点让人看不懂。毕竟我是受晴神的影响比较大的。

后记

最近刚好在刷DFS的题目,发现PAT的这道题目还是比较有意思的,并且和我们的生活息息相关。又让我燃起了刷算法的热情O(∩_∩)O

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值