hiho 1643 最少换乘 O(n)复杂度

#1643 : 最少换乘

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

小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。

样例输入
3 123 345  
4 321 375 123 456  
4 222 333 123 444  
2 222 345
样例输出
1

思路:官方题解用的思路是最短路径,我是直接用的广搜解决。从起点所在的线路开始向临近线路搜索,先搜到

目标站点所在线路的搜索路线,就是换乘最少的路线。挑了几份最短路的代码提交比较时间,是比最短路算法快

一点的,内存耗费也稍微少点。搜索的时候,对搜过的线路,搜过的站点都做标记,保证只搜一遍,复杂度就

相当于遍历一遍输入的所有站点(包括重复的)。详细实现原理看代码注释


#include 
using namespace std;
typedef long long ll;
typedef pair pii;
#define INF 0x3f3f3f3f
#define FI first
#define SE second
int mod = 1000000007;
const int N = 500010;
vector rec[N];  //第i个站点所在的线路
vector 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 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 > Q;
        Q.push(pii(mp[s], 0));   //队列元素对应一个站点及到达这个站点的换乘次数
        int ans = -1;
        vis[mp[s]] = true;
        while (!Q.empty())
        {
            pii u = Q.front();Q.pop();
            vector &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)的复杂度。


你可能感兴趣的:(acm,hihocoder,搜索)