1. 程式人生 > >hiho 1643 最少換乘 O(n)複雜度

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 <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)的複雜度。