#1643 : 最少换乘
-
3 123 345 4 321 375 123 456 4 222 333 123 444 2 222 345
样例输出
-
1
描述
小Ho居住的城市有N条公交车线路,其中第i条线路上有Ki个车站。
某些线路之间会有公共的车站,小Ho可以在这些车站从一条线路换乘到另一条线路。
现在给定N条公交车线路以及两个车站S和E,你能帮助小Ho计算从S到E最少换乘几次公交车吗?
输入
第一行包含三个整数N,S和E。
以下N行每行描述一条线路。第一个整数Ki代表该条线路包含的车站数。之后Ki个整数代表车站的编号。
注意车站编号不一定连续。
对于50%的数据,1 ≤ N ≤ 1000, 1 ≤ Ki ≤ 100
对于100%的数据,1 ≤ N ≤ 50000, 1 ≤ Ki ≤ 80000,1 ≤ 所有Ki之和 ≤ 500000, 1 ≤ 车站编号 ≤ 5000000。
输出
输出最少换乘次数。如果S到E不可达,输出-1。
思路:官方题解用的思路是最短路径,我是直接用的广搜解决。从起点所在的线路开始向临近线路搜索,先搜到
目标站点所在线路的搜索路线,就是换乘最少的路线。挑了几份最短路的代码提交比较时间,是比最短路算法快
一点的,内存耗费也稍微少点。搜索的时候,对搜过的线路,搜过的站点都做标记,保证只搜一遍,复杂度就
相当于遍历一遍输入的所有站点(包括重复的)。详细实现原理看代码注释
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define INF 0x3f3f3f3f
#define FI first
#define SE second
int mod = 1000000007;
const int N = 500010;
vector<int> rec[N]; //第i个站点所在的线路
vector<int> load[N]; //第i条线路上的站点
bool yes[N]; //第i条线路是否是目标站点所在线路
bool vis[N]; //记录第i个车站是否被遍历过
bool line[N]; //记录第i条线路是否被遍历过
int main()
{
// freopen("in.txt", "r", stdin);
int n;
while (~scanf("%d", &n))
{
int s, e, k, idx = 0;
map<int, int> mp; //站点编号范围太大了,实际站点个数很少离散处理一下
scanf("%d%d", &s, &e);
mp.clear();
mp[s] = ++idx;
mp[e] = ++idx;
for (int i = 1; i <= n; i++)
{
load[i].clear();
rec[i].clear();
vis[i] = 0;
line[i] = 0;
}
for (int i = 1; i <= n; i++)
{
yes[i] = 0;
scanf("%d", &k);
for (int j = 0; j < k; j++)
{
int x;
scanf("%d", &x);
load[i].push_back(x); //记录第i条线所对应的站点
if (x == e)
yes[i] = true; //第i条线路有目标站点,赋值true
if (mp.find(x) == mp.end())
mp[x] = ++idx;
rec[mp[x]].push_back(i); //x站点新增穿过它的线路
}
}
if (s == e)
{
puts("0");
continue;
}
queue<pair<int, int> > Q;
Q.push(pii(mp[s], 0)); //队列元素对应一个站点及到达这个站点的换乘次数
int ans = -1;
vis[mp[s]] = true;
while (!Q.empty())
{
pii u = Q.front();Q.pop();
vector<int> &vec = rec[u.FI];
for (int i = 0; i < vec.size(); i++)
{
int v = vec[i];
if (yes[v]) //如果当前站点对应的某条线路含有目标站点,则找到了,跳出循环
{
ans = u.SE;
goto here;
}
if (line[v]) //该线路遍历过则不去扩展
continue;
line[v] = true;
for (int j = 0; j < load[v].size(); j++)//扩展这条线路对应的其它站点,
{ //因为队首站点之前的走过的线路
int vc = mp[load[v][j]]; //都被标记过了,没标记的
if (!vis[vc]) //都是没走过的,就相当于换乘一次了,
{
vis[vc] = true;
Q.push(make_pair(vc, u.SE+1)); //换乘次数加1
}
}
}
}
here:
printf("%d\n", ans);
}
return 0;
}
复杂度分析:从代码看,由于做了标记每个点只入队出队一次,而每个点出队后,不可避免要遍历一遍这个点对应了
多少条线,每个点对应线路不同,不能直接乘法来计算;换个角度看,能遍历得到的线路说明这条线上有这个站点,
总遍历次数其实就是所给出的n条线里面所有的站点数。即所有ki的和 <=500000。
而遍历每条线路的代码是独立开的,因为每条线最多访问一次(可能有的线没被访问到就找到答案退出了)。
所以总次数也是所有ki的总和<=500000。算法就是遍历了所有输入点2次,是O(N)的复杂度。