1. 程式人生 > 實用技巧 >[luogu p2296] 尋找道路

[luogu p2296] 尋找道路

傳送門

尋找道路

題目描述

在有向圖 \(G\) 中,每條邊的長度均為 \(1\),現給定起點和終點,請你在圖中找一條從起點到終點的路徑,該路徑滿足以下條件:

  1. 路徑上的所有點的出邊所指向的點都直接或間接與終點連通。
  2. 在滿足條件$ 1 $的情況下使路徑最短。

注意:圖 \(G\) 中可能存在重邊和自環,題目保證終點沒有出邊。

請你輸出符合條件的路徑的長度。

輸入輸出格式

輸入格式

第一行有兩個用一個空格隔開的整數 \(n\)\(m\),表示圖有 \(n\) 個點和 \(m\) 條邊。

接下來的 \(m\) 行每行 \(2\) 個整數 \(x,y\),之間用一個空格隔開,表示有一條邊從點 \(x\)

指向點\(y\)

最後一行有兩個用一個空格隔開的整數 \(s, t\),表示起點為 \(s\),終點為 \(t\)

輸出格式

輸出只有一行,包含一個整數,表示滿足題目描述的最短路徑的長度。如果這樣的路徑不存在,輸出\(-1\)

輸入輸出樣例

輸入樣例 #1

3 2
1 2
2 1
1 3

輸出樣例 #1

-1

輸入樣例 #2

6 6
1 2
1 3
2 6
2 5
4 5
3 4
1 5

輸出樣例 #2

3

說明

解釋1:

如上圖所示,箭頭表示有向道路,圓點表示城市。起點$1 $與終點\(3\)不連通,所以滿足題目描述的路徑不存在,故輸出\(-1\)

解釋2:

如上圖所示,滿足條件的路徑為\(1\)

- >\(3\)- >\(4\)- >\(5\)。注意點\(2\) 不能在答案路徑中,因為點\(2\)連了一條邊到點\(6\) ,而點\(6\) 不與終點\(5\) 連通。

【資料範圍】

對於\(30\%\)的資料,\(0 < n \le 10\)\(0 < m \le 20\);

對於\(60\%\)的資料,\(0 < n \le 100\)\(0 < m \le 2000\);

對於\(100\%\)的資料,\(0 < n \le 10000, 0 < m \le 200000,0 < x,y,s,t \le n, x,s \ne t\)

分析

此題是一道很好的思路題。看似是一道圖論,但這題不重在圖論,而在搜尋。(所以我沒打圖論的tag)

首先,一個點和終點的關係,在此題中可以有三種層層遞進的特徵:

  1. 無關係。(任意一個點)
  2. 與終點聯通的點。
  3. 指向的節點都與終點聯通的點。

顯然題目要我們求的是,由3這種點組成的,連線起點和終點的最短路徑。

什麼是層層遞進呢?也就是說,1這種點包含2這種點,2這種點包含3這種點。

那麼此題就可以分作兩步:

  • 找滿足3條件的點
  • 求最短路徑

求最短路徑很簡單,況且此題中所有邊權都是1,你甚至不用什麼最短路演算法,直接bfs就可以解決。(如果需要最短路,我就會打上圖論的tag了)

目前的問題是,怎麼找滿足3條件的點。其實剛剛已經剖析了,既然1包含2,2包含3,我們就可以在1中直接找2,然後再在2中找3就可以了。

找到2後,3就很好找了,直接判斷這個點的所有邊指向的節點是否滿足2即可。

問題又變成了,怎麼找2

其實很簡單,我們只需要反向建邊,反向bfs就可以了。能反bfs到的點就是2點。

所以說,這題思路很妙,但是並不難想吧。

直接上程式碼:

程式碼

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-09-05 09:20:58 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-09-06 15:05:15
 */
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>

const int maxn = 10005;
bool valid[maxn], link_end[maxn];

//valid是3點,link_end是2點。如果valid[i]是true代表i點為3點,link_end陣列同理

int dis[maxn];//此點到起點的距離

std :: vector <int> side[maxn];//邊
std :: vector <int> rev_side[maxn];//反向邊

int main() {
    int n, m;
    std :: scanf("%d%d", &n, &m);

    for (int i = 1; i <= m; ++i) {
        int u, v;
        std :: scanf("%d%d", &u, &v);
        side[u].push_back(v);
        rev_side[v].push_back(u);
    }

    int s, t;
    std :: scanf("%d%d", &s, &t);
    link_end[t] = true;
    std :: queue <int> q;

    q.push(t);
    while (!q.empty()) {
        int now = q.front();
        q.pop();
        for (int i = rev_side[now].size() - 1; i >= 0; --i) {
            int v = rev_side[now][i];
            if (!link_end[v]) {
                q.push(v);
                link_end[v] = true;
            }
        }
    }

    if (!link_end[s]) {//注意,如果起始點就不和結尾點聯通,那就不可能會有合法路徑,直接輸出-1即可
        std :: printf("-1\n");
        return 0;
    }

    for (int i = 1; i <= n; ++i)
        if (link_end[i]) {
            valid[i] = true;
            for (int j = side[i].size() - 1; j >= 0; --j) {
                int v = side[i][j];
                if (!link_end[v]) {
                    valid[i] = false;
                    break;
                }
            }
        }

    if (!valid[s]) {//同理,如果起始點就不滿足3點的條件,也應該直接輸出-1
        std :: printf("-1\n");
        return 0;
    }

    dis[s] = 1;
    while (!q.empty()) 
        q.pop();
    q.push(s);
    while (!q.empty()) {
        int now = q.front();
        q.pop();
        if (now == t) {
            std :: printf("%d\n", dis[t] - 1);
            return 0;
        }

        for (int i = side[now].size() - 1; i >= 0; --i) {
            int v = side[now][i];
            if (valid[v] && !dis[v]) {
                dis[v] = dis[now] + 1;
                q.push(v);
            }
        }
    }

    std :: printf("-1\n");//如果無路,-1
    return 0;
}

評測記錄

評測記錄