PAT 甲级 1131 Subway Map(图遍历,二尺度)

Take Line#X1 from S1 to S2.
Take Line#X2 from S2 to S3.
......

where Xi's are the line numbers and Si's are the station indices. Note: Besides the starting and ending stations, only the transfer stations shall be printed.

If the quickest path is not unique, output the one with the minimum number of transfers, which is guaranteed to be unique.

Sample Input:

4
7 1001 3212 1003 1204 1005 1306 7797
9 9988 2333 1204 2006 2005 2004 2003 2302 2001
13 3011 3812 3013 3001 1306 3003 2333 3066 3212 3008 2302 3010 3011
4 6666 8432 4011 1306
3
3011 3013
6666 2001
2004 3001

Sample Output:

2
Take Line#3 from 3011 to 3013.
10
Take Line#4 from 6666 to 1306.
Take Line#3 from 1306 to 2302.
Take Line#2 from 2302 to 2001.
6
Take Line#2 from 2004 to 1204.
Take Line#1 from 1204 to 1306.
Take Line#3 from 1306 to 3001.

 题目大意

给出一张地铁线路图。

一共有N条线路,每条线路都有若干个站点。

一个站点可能可以换乘多条线路,但最多不超过五个。并且不存在自循环的情况

(即自己链接自己)

要求,①经过站点最少的线路;②换程次数要最少

分析

经过站点最少就是经典的最短路径问题,可以用Dijkstra或者SPFA。

本题最麻烦的点在于要求换程线路最少。

换程线路最好用边来记录,即记录每条边属于哪条线路。用站点记录会存在难以匹配的问题。

解法一

本来想看柳神的,但是实在有点看不懂,就去网上搜了一下其他大佬的代码。

【题解】【PAT甲】1131 Subway Map (30 分)(dfs)_pat甲1131_Elephant_King的博客-CSDN博客

后来看到这位大佬的。

这位大佬的代码也是参考柳神的,但每一步都有注释。虽然相对复杂一些,但更容易看懂。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
map<pair<int,int>,int> touch;  //保存每条边属于哪条线路
map<int,vector<int>> ma;       //保存图的邻接表
vector<int> tempPath;          //保存临时路径
int visited[10005]; 
int mmincnt=INF;
int be,en;                     //起点和终点
int minTranfer=INF;            
vector<int> path;
int mintransfer(vector<int> v){		//计算倒几班车 
	int cnt=-1;
	int preLine=-1;		//只要不等于上一条线就不行 
	for(int i=1;i<v.size();i++){
		if(touch[make_pair(v[i-1],v[i])]!=preLine)	cnt++;
		preLine = touch[make_pair(v[i-1],v[i])];  //更新
	}
	return cnt;
}

//dfs直接搜就可以
void dfs(int x,int cnt){
	if(x==en&&(cnt<mmincnt||cnt==mmincnt&&mintransfer(tempPath)<minTranfer)){
		mmincnt=cnt;
		minTranfer=mintransfer(tempPath);
		path=tempPath;
	}
	if(x==en)	return;
	if(cnt>=mmincnt)	return ;
	for(int i=0;i<ma[x].size();i++){
		if(visited[ma[x][i]]==0){
			visited[ma[x][i]]=1;
			tempPath.push_back(ma[x][i]);
			dfs(ma[x][i],cnt+1);
			tempPath.pop_back();
			visited[ma[x][i]]=0;	
		} 
	}
}
int main(){
	int n;
	cin>>n;
	int data;
	int pre;
	for(int i=1;i<=n;i++){
		int m;
		scanf("%d",&m);
		for(int j=0;j<m;j++){
			scanf("%d",&data);
			if(j!=0){
				ma[data].push_back(pre);
				ma[pre].push_back(data);
				touch[make_pair(pre,data)]=touch[make_pair(data,pre)]=i;//保存对应的线路
			}
			pre=data;
		}
	}
	int k;
	scanf("%d",&k);
	for(int i=0;i<k;i++){
		scanf("%d %d",&be,&en);
		mmincnt=INF;               //这几个参数!!每次query完都要重置!!不然报错!!
		minTranfer=INF;
		tempPath.clear();
		tempPath.push_back(be);
		visited[be]=1;
		dfs(be,0);
		visited[be]=0;
		printf("%d\n",mmincnt);
		int preTransfer=be;       //保存中转站
		int preLine=0;            //保存上一条线路
		for(int j=1;j<path.size();j++){
			if(touch[make_pair(path[j-1],path[j])]!=preLine){
				if(preLine!=0)
					printf("Take Line#%d from %04d to %04d.\n",preLine,preTransfer,path[j-1]);
				preLine=touch[make_pair(path[j-1],path[j])];
				preTransfer = path[j-1];
			}
		}
		printf("Take Line#%d from %04d to %04d.\n", preLine, preTransfer, en);
	}
}

 

解法二

严格意义上不能算第二种解法。

只是尝试了用Dijkstra二尺度+DFS来重写了一遍。线路的记录思路参考了上面那位大佬的。

因为代码更复杂了一点,所以大家看看就好。感觉直接用DFS可能更方便一些这道题

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
const int inf=1e6;

struct Point{
    int num,dis;
    Point(int a,int b):num(a),dis(b){}
    bool operator < (const Point k) const{
        return dis>k.dis;
    }
};
map<pair<int,int>,int> lines;  //保存每条边属于哪条路线
vector<int> stop[maxn]; //保存站点
vector<int> tempPath,Path,pre[maxn];
int vis[maxn]={false},dis[maxn];
int minTrans=inf;

void Dijkstra(int s){
    fill(vis,vis+maxn,false);
    fill(dis,dis+maxn,inf);
    priority_queue<Point> q;
    dis[s]=0;
    q.push(Point(s,0));
    while(!q.empty()){
        int now=q.top().num;
        q.pop();
        if(vis[now]) continue;
        vis[now]=true;
        for(int i=0;i<stop[now].size();++i){
            int t=stop[now][i];
            if(dis[now]+1<dis[t]){
                dis[t]=dis[now]+1;
                pre[t].clear();
                pre[t].push_back(now);
                q.push(Point(t,dis[t]));
            }else if(dis[now]+1==dis[t]){
                pre[t].push_back(now);
            }
        }
    }
}

int countTrans(vector<int> tmp){
    int Tcnt=1;
    for(int i=1;i<tmp.size()-1;++i){
        if(lines[make_pair(tmp[i-1],tmp[i])]!=lines[make_pair(tmp[i],tmp[i+1])]){
            Tcnt++;
        }
    }
    return Tcnt;
}

void DFS(int s,int e){
    if(s==e){
        int t=countTrans(tempPath);
        if(t<minTrans){
            minTrans=t;
            Path=tempPath;
        }
        return ;
    }
    for(int i=0;i<pre[s].size();++i){
        int to=pre[s][i];
        tempPath.push_back(to);
        DFS(to,e);
        tempPath.pop_back();
    }
    return ;
}
int main(){
    int n;cin >> n;
    for(int i=1;i<=n;++i){
        int tmp,from,to;
        cin >> tmp >> from;
        for(int j=1;j<tmp;++j){
            cin >> to;
            stop[from].push_back(to);
            stop[to].push_back(from);
            lines[make_pair(from,to)]=lines[make_pair(to,from)]=i;
            from=to;
        }
    }
    int q;cin >> q;
    while(q--){
        tempPath.clear();
        Path.clear();
        minTrans=inf;
        int from,to;
        cin >> from >>to;
        Dijkstra(from);
        cout << dis[to] <<"\n";
        tempPath.push_back(to);
        DFS(to,from);
        int preLine=0;
        int preTrans=0;
        for(int i=Path.size()-2;i>=0;--i){
            if(lines[make_pair(Path[i+1],Path[i])]!=preTrans){
                if(preTrans!=0) printf("Take Line#%d from %04d to %04d.\n",preTrans,preLine,Path[i+1]);
                preLine=Path[i+1];
                preTrans=lines[make_pair(Path[i+1],Path[i])];
            }
        }
        printf("Take Line#%d from %04d to %04d.\n",preTrans,preLine,to);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值