ABC041-D:徒競走
問題
https://beta.atcoder.jp/contests/abc041/tasks/abc041_d
N 頂点上のグラフに、M本の有向辺が与えられる。M本の有向辺の条件を満たし、N 個の頂点を左から右へ横一列に並べる並べ方は何通りか。
制約
- 2 <= N <= 16
- 1 <= M <= N(N-1)/2
考え方
トポロジカルソートの数え上げ問題である。頂点集合を S としてDAG上の右から順序を確定していくこととする。
サンプル1を例に考える。 S = {1, 2, 3} である。 頂点集合 S をトポロジカルソートする並べ方を f(S) を表すこととする。空集合をトポロジカルソートする方法の通り数は f(φ) = 1 とする。S をトポロジカルソートをするとき、Sの中で一番右になれる頂点は {1, 3} のいずれかである。よって、
f({1, 2, 3}) = f({2, 3}) + f({1, 2})
= f({2}) + f({2})
= f(φ) + f(φ)
= 1 + 1 = 2
となる。
f({2, 3}) は 頂点 1 が一番右においた場合、 f({1, 2}) は頂点 3 を一番右においた場合である。頂点集合 S の中でトポロジカルソートしたときに一番右になることのできる頂点 i の条件は、 j ∈ S なる他の頂点 j について i から j への有向辺がないことである。この計算量は O(N2 2N) である。事前に各頂点に対して有向辺が存在する頂点を計算しておくと、ビットの論理積をとることでで判定することができるので O(N 2N) になる。
int n, m; long[] s = new long[1<<n]; boolean[][] mat; long[] memo; public void solve(int testNumber, InputReader in, PrintWriter out) { n = in.nextInt(); m = in.nextInt(); mat = new boolean[n][n]; memo = new long[1<<n]; Arrays.fill(memo, -1); for (int i = 0; i < m; i++) { int x = in.nextInt()-1, y = in.nextInt()-1; mat[x][y] = true; } long ans = rec((1<<n) - 1); out.println(ans); } long rec(int state) { if (memo[state] != -1) { return memo[state]; } if (Integer.bitCount(state) == 0) { return 1; } long ret = 0; for (int i = 0; i < n; i++) { if (((state >> i) & 1) == 1) { boolean ok = true; for (int j = 0; j < n; j++) { if (i == j) continue; if (((state >> j) & 1) == 1) { if (mat[i][j]) { ok = false; } } } if (ok) { ret += rec(state - (1<<i)); } } } return memo[state] = ret; }
ポイント
bitDPによってナイーブ解が O(N!) になる問題を O(N * 2N) 程度に落とせる。状態を集合として取ることで、順序関係を気にしなくて良くなるため計算量が落ちるため。加えて、頂点集合 2N に対して、各ビットを全探索することが多いイメージである。