Official

E - Count Simple Paths Editorial by Nyaan


この問題は「パスの本数」という慣れない数え上げを要求する問題で、上手く解けずに困ってしまった方が多いかもしれません。ところが実は、C 問題でも出てきた深さ優先探索 (DFS) を応用すれば、素朴なアルゴリズムでこの問題を実装することができます。
具体的には、以下の Python 風の疑似コードで表せる DFS を実装します。(訪問したかどうかを管理する変数 vis のフラグの立て方が通常の dfs と少し異なるのがポイントです。)

# g : グラフの隣接リスト。ただし頂点の番号はすべて真の番号から 1 を引いている

vis = [False] * N
path = []
answer = 0
limit = 1000000
path = []

def dfs(c):
  if answer == limit:
    return

  vis[c] = True
  path.append(c)
  answer += 1
  
  for d in g[c]:
    if vis[d] == False:
      dfs(d)

  vis[c] = False
  path.pop()

dfs(0)

このDFS は、 1 つの状態に 1 つのパスが対応しています。具体的には DFS 内部の path に格納されている頂点の列として表せるパスが DFS の状態と対応しています。

  • ここでの「状態」というのは、DFS の遷移の過程を根付き木として表したときの各頂点のことを指します。また、状態とパスが 1 対 1 で対応しているのは帰納法により証明できます。

よって DFS で訪問する状態の個数が答えとなり、これは実際に DFS を動作させれば調べられます。ただし、パスの本数が \(10^6\) 以上になった場合はその時点で DFS を打ち切り、答えは \(10^6\) となります。

計算量を考えます。疑似コードの関数 dfs は高々 \(10^6\) 回しか呼び出されず、dfs 内部での計算量は頂点 \(c\) の次数 \(\mathrm{deg}(c)\) で抑えられます。各頂点の次数は高々 \(10\) なので、ステップ数は \(10^6 \times 10 = 10^7\) オーダーで抑えられるため、このアルゴリズムは ACするのに十分高速に動作します。

  • 実装例 (C++)
#include <iostream>
#include <vector>
using namespace std;

int calc(int N, vector<vector<int>> g) {
  int ans = 0, limit = 1000000;
  vector<int> vis(N);
  auto dfs = [&](auto rc, int c) -> void {
    if (ans == limit) return;
    ans++;
    vis[c] = true;
    for (auto& d : g[c]) {
      if (vis[d]) continue;
      rc(rc, d);
    }
    vis[c] = false;
  };
  dfs(dfs, 0);
  return ans;
}

int main() {
  int N, M;
  cin >> N >> M;
  vector<vector<int>> g(N);
  for (int i = 0; i < M; i++) {
    int u, v;
    cin >> u >> v;
    --u, --v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  cout << calc(N, g) << "\n";
}

posted:
last update: