{ }codelper
動的計画法グラフ(隣接リスト)トポロジカルソートP0154

水が流れていく方法の数

田んぼに水を引く区画が n 個あり、1 番から n 番まで番号がついている。
区画の間には水路が m 本ある。
水路一つ "a b" は、水が a 番の区画から b 番の区画へだけ流れることを表す。
同じ二つの区画の間に水路がいくつもあることがあり、同じ二つの区画をつなぐ水路でも、違う水路は違う水路として数える。
どの区画から水路をたどって流れても、その区画に戻ってくることはない。
1 番の区画(泉)に水を注ぐと、水は今いる区画から出る水路の一つを選んで流れることを繰り返し、出る水路のない区画に着くとそこにたまって止まる。
水が流れていく方法が全部で何通りあるか求めよ。
通る水路の順番が違えば違う方法として数える。
1 番の区画から出る水路がなければ、水は 1 番の区画にそのままたまり、これも一通りの方法である。
数がとても大きくなることがあるので、1,000,000,007 で割った余りを出力する。

入力
一行目に n と m、続く m 行にそれぞれ a と b が空白区切りで与えられる。
1 ≤ n ≤ 10,000、0 ≤ m ≤ 10,000、1 ≤ a, b ≤ n、a ≠ b。
水路をたどって戻ってくる輪はない。
出力
水が流れていく方法の数を 1,000,000,007 で割った余り
例 1 · stdin
5 6
1 2
1 3
2 4
3 4
4 5
1 5
stdout
3
例 2 · stdin
6 6
1 2
1 3
2 4
3 4
3 5
6 2
stdout
3
例 3 · stdin
3 0
stdout
1
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

ways[v] =「1 番から v まで流れてくる方法の数」とすると、v に入ってくる水路ごとにその出発区画の ways を足せば済みます(動的計画法)。
答えは、出る水路のない区画(水がたまる区画)ごとに ways を足した値です — 1 番の区画から出る水路がなければ 1 番もそういう区画なので、ways[1] = 1 がそのまま答えになります(三つ目の例)。
ただし足す前に出発区画の値が出そろっている必要があるので、入ってくる水路がすべて計算済みの区画から取り出すトポロジカルソート(カーンのアルゴリズム — inflow を数え、0 になった区画をキューに入れます)の順に流します。
落とし穴その一:キューに 1 番だけを入れて始めると、1 番から行けない区画(二つ目の例の 6 番)から入る水路が inflow に残り、その下の区画がいつまでも取り出されません — 入ってくる水路のない区画をすべて入れます(その区画の ways は 0 なので答えは変わりません)。
落とし穴その二:番号順に流すと間違えます — 番号の小さい区画が上にあるという約束はありません。
落とし穴その三:水を注ぐ 1 番の区画を、たまる区画から外すと、三つ目の例で 0 になります。
落とし穴その四:方法の数は 2^2500 のように大きくなります。
足すたびに余りを求める必要があります:C・C++ の long long でも最後に一回だけ割るとあふれ、Python はあふれませんが割らなければ大きな数をそのまま出力します。
余り二つの和は約 20 億で 32 ビットの int(約 21 億)にぎりぎり収まるので、余裕を持って C・C++ は long long、Java・C# は long に入れ、JavaScript・TypeScript・Craykon の数は 2^53 まで正確なのでそのままで大丈夫です。
落とし穴その五:再帰で count(v) をメモしながら数えると、10,000 区画の鎖で Python は既定の再帰上限(1,000)を超えて RecursionError になり、Craykon も 4,000 段ほどで止まります — キューで回るトポロジカルソートには深さがありません。
キューも言語ごとに違います:Python collections.deque、Java ArrayDeque<Integer>、C# Queue<int>、C++ std::queue<int>、C は配列と先頭・末尾の位置、JavaScript・TypeScript・Craykon はリストに追加するだけで先頭の位置(head)を動かします(shift() は毎回詰めるので遅いです)。
Craykon はリストが 10,000 個までなので番号から 1 を引いて 0 番から置きます(新リスト(n, 0))。

Pythonmain.py · 32 行
import sysfrom collections import deque MOD = 1_000_000_007data = sys.stdin.read().split()n, m = int(data[0]), int(data[1]) down = [[] for _ in range(n + 1)]  # down[a] = a에서 물이 흘러가는 칸들(물길마다 한 번씩)inflow = [0] * (n + 1)  # 아직 계산하지 않은, 들어오는 물길의 수for i in range(m):    a, b = int(data[2 + 2 * i]), int(data[3 + 2 * i])    down[a].append(b)    inflow[b] += 1 # 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 차례로 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)ways = [0] * (n + 1)  # ways[v] = 1번에서 v까지 흐르는 방법의 수(MOD로 나눈 나머지)ways[1] = 1ready = deque(v for v in range(1, n + 1) if inflow[v] == 0)while ready:    v = ready.popleft()    for nxt in down[v]:        ways[nxt] = (ways[nxt] + ways[v]) % MOD        inflow[nxt] -= 1        if inflow[nxt] == 0:            ready.append(nxt) # 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다total = 0for v in range(1, n + 1):    if not down[v]:        total = (total + ways[v]) % MODprint(total)
JavaScriptmain.js · 28 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;const MOD = 1000000007; const down = Array.from({ length: n + 1 }, () => []); // down[a] = a에서 물이 흘러가는 칸들(물길마다 한 번씩)const inflow = new Array(n + 1).fill(0); // 아직 계산하지 않은, 들어오는 물길의 수for (let i = 0; i < m; i++) {  const a = data[2 + 2 * i], b = data[3 + 2 * i];  down[a].push(b);  inflow[b]++;} // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)const ways = new Array(n + 1).fill(0); // ways[v] = 1번에서 v까지 흐르는 방법의 수(MOD로 나눈 나머지)ways[1] = 1;const queue = [];for (let v = 1; v <= n; v++) if (inflow[v] === 0) queue.push(v);for (let head = 0; head < queue.length; head++) {  const v = queue[head];  for (const next of down[v]) {    ways[next] = (ways[next] + ways[v]) % MOD; // 두 수 모두 MOD보다 작아 합이 2^53보다 훨씬 작다    if (--inflow[next] === 0) queue.push(next);  }}// 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다let total = 0;for (let v = 1; v <= n; v++) if (down[v].length === 0) total = (total + ways[v]) % MOD;console.log(total);
TypeScriptmain.ts · 30 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m] = data;const MOD = 1000000007; const down: number[][] = Array.from({ length: n + 1 }, (): number[] => []); // down[a] = a에서 물이 흘러가는 칸들const inflow: number[] = new Array<number>(n + 1).fill(0); // 아직 계산하지 않은, 들어오는 물길의 수for (let i = 0; i < m; i++) {  const a = data[2 + 2 * i], b = data[3 + 2 * i];  down[a].push(b);  inflow[b]++;} // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)const ways: number[] = new Array<number>(n + 1).fill(0); // ways[v] = 1번에서 v까지 흐르는 방법의 수(MOD로 나눈 나머지)ways[1] = 1;const queue: number[] = [];for (let v = 1; v <= n; v++) if (inflow[v] === 0) queue.push(v);for (let head = 0; head < queue.length; head++) {  const v = queue[head];  for (const next of down[v]) {    ways[next] = (ways[next] + ways[v]) % MOD; // 두 수 모두 MOD보다 작아 합이 2^53보다 훨씬 작다    if (--inflow[next] === 0) queue.push(next);  }}// 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다let total = 0;for (let v = 1; v <= n; v++) if (down[v].length === 0) total = (total + ways[v]) % MOD;console.log(total);
JavaMain.java · 46 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.List;import java.util.StringTokenizer; public class Main {    static final int MOD = 1_000_000_007;     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        int n = Integer.parseInt(st.nextToken());        int m = Integer.parseInt(st.nextToken());         List<List<Integer>> down = new ArrayList<>(); // down.get(a) = a에서 물이 흘러가는 칸들        for (int v = 0; v <= n; v++) down.add(new ArrayList<>());        int[] inflow = new int[n + 1]; // 아직 계산하지 않은, 들어오는 물길의 수        for (int i = 0; i < m; i++) {            st = new StringTokenizer(in.readLine());            int a = Integer.parseInt(st.nextToken());            int b = Integer.parseInt(st.nextToken());            down.get(a).add(b);            inflow[b]++;        }         // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)        long[] ways = new long[n + 1]; // 두 나머지의 합은 int를 넘을 수 있어 long        ways[1] = 1;        ArrayDeque<Integer> ready = new ArrayDeque<>();        for (int v = 1; v <= n; v++) if (inflow[v] == 0) ready.add(v);        while (!ready.isEmpty()) {            int v = ready.poll();            for (int next : down.get(v)) {                ways[next] = (ways[next] + ways[v]) % MOD;                if (--inflow[next] == 0) ready.add(next);            }        }        // 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다        long total = 0;        for (int v = 1; v <= n; v++) if (down.get(v).isEmpty()) total = (total + ways[v]) % MOD;        System.out.println(total);    }}
C#Main.cs · 44 行
using System;using System.Collections.Generic; class Program{    const long MOD = 1_000_000_007;     static void Main()    {        string[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(first[0]);        int m = int.Parse(first[1]);         var down = new List<int>[n + 1]; // down[a] = a에서 물이 흘러가는 칸들(물길마다 한 번씩)        for (int v = 0; v <= n; v++) down[v] = new List<int>();        var inflow = new int[n + 1]; // 아직 계산하지 않은, 들어오는 물길의 수        for (int i = 0; i < m; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            int a = int.Parse(p[0]), b = int.Parse(p[1]);            down[a].Add(b);            inflow[b]++;        }         // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)        var ways = new long[n + 1]; // 두 나머지의 합은 int를 넘을 수 있어 long        ways[1] = 1;        var ready = new Queue<int>();        for (int v = 1; v <= n; v++) if (inflow[v] == 0) ready.Enqueue(v);        while (ready.Count > 0)        {            int v = ready.Dequeue();            foreach (int next in down[v])            {                ways[next] = (ways[next] + ways[v]) % MOD;                if (--inflow[next] == 0) ready.Enqueue(next);            }        }        // 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다        long total = 0;        for (int v = 1; v <= n; v++) if (down[v].Count == 0) total = (total + ways[v]) % MOD;        Console.WriteLine(total);    }}
C++main.cpp · 40 行
#include <iostream>#include <queue>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    const long long MOD = 1000000007;    int n, m;    std::cin >> n >> m;     std::vector<std::vector<int>> down(n + 1); // down[a] = a에서 물이 흘러가는 칸들(물길마다 한 번씩)    std::vector<int> inflow(n + 1, 0);         // 아직 계산하지 않은, 들어오는 물길의 수    for (int i = 0; i < m; i++) {        int a, b;        std::cin >> a >> b;        down[a].push_back(b);        inflow[b]++;    }     // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)    std::vector<long long> ways(n + 1, 0); // 두 나머지의 합은 int를 넘을 수 있어 long long    ways[1] = 1;    std::queue<int> ready;    for (int v = 1; v <= n; v++)        if (inflow[v] == 0) ready.push(v);    while (!ready.empty()) {        int v = ready.front();        ready.pop();        for (int next : down[v]) {            ways[next] = (ways[next] + ways[v]) % MOD;            if (--inflow[next] == 0) ready.push(next);        }    }    // 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다    long long total = 0;    for (int v = 1; v <= n; v++)        if (down[v].empty()) total = (total + ways[v]) % MOD;    std::cout << total << "\n";}
Cmain.c · 45 行
#include <stdio.h> #define MAXN 10001#define MAXM 10000#define MOD 1000000007LL /* 인접 목록을 배열로: head[a]는 a에서 나가는 첫 물길 칸, next[e]는 다음 칸(-1이면 끝) */static int head[MAXN], next[MAXM], to[MAXM];static int inflow[MAXN];  /* 아직 계산하지 않은, 들어오는 물길의 수 */static long long ways[MAXN]; /* 두 나머지의 합은 int를 넘을 수 있어 long long */static int queue[MAXN]; int main(void) {    int n, m;    scanf("%d %d", &n, &m);    for (int v = 1; v <= n; v++) head[v] = -1;    for (int e = 0; e < m; e++) {        int a, b;        scanf("%d %d", &a, &b);        to[e] = b;        next[e] = head[a];        head[a] = e;        inflow[b]++;    }     /* 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다) */    ways[1] = 1;    int qh = 0, qt = 0;    for (int v = 1; v <= n; v++)        if (inflow[v] == 0) queue[qt++] = v;    while (qh < qt) {        int v = queue[qh++];        for (int e = head[v]; e != -1; e = next[e]) {            int b = to[e];            ways[b] = (ways[b] + ways[v]) % MOD;            if (--inflow[b] == 0) queue[qt++] = b;        }    }    /* 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다 */    long long total = 0;    for (int v = 1; v <= n; v++)        if (head[v] == -1) total = (total + ways[v]) % MOD;    printf("%lld\n", total);    return 0;}
Craykonmain.cray · 53 行
MOD = 1000000007;첫줄 = コンソール.数読み();n = 첫줄[0];m = 첫줄[1]; // 목록은 10,000칸까지라 칸 번호에서 1을 빼 0번부터 둔다down = []; // down[a] = a에서 물이 흘러가는 칸들(물길마다 한 번씩)繰り返す (v = 0; v < n; v++){    down.追加([]);}inflow = 新リスト(n, 0); // 아직 계산하지 않은, 들어오는 물길의 수繰り返す (i = 0; i < m; i++){    물길 = コンソール.数読み();    down[물길[0] - 1].追加(물길[1] - 1);    inflow[물길[1] - 1] += 1;} // 위상 정렬: 들어오는 물길이 모두 계산된 칸부터 꺼낸다(1번에서 못 가는 칸도 모두 넣는다)ways = 新リスト(n, 0); // ways[v] = 1번에서 v까지 흐르는 방법의 수(MOD로 나눈 나머지)ways[0] = 1;queue = [];繰り返す (v = 0; v < n; v++){    もし (inflow[v] == 0)    {        queue.追加(v);    }}繰り返す (head = 0; head < queue.個数; head++){    v = queue[head];    それぞれ (next の中 down[v])    {        ways[next] = (ways[next] + ways[v]) % MOD; // 두 수 모두 MOD보다 작아 합이 2^53보다 훨씬 작다        inflow[next] -= 1;        もし (inflow[next] == 0)        {            queue.追加(next);        }    }}// 물은 나가는 물길이 없는 칸에 고여 멈춘다: 그런 칸마다 그 칸까지 오는 길의 수를 더한다total = 0;繰り返す (v = 0; v < n; v++){    もし (down[v].個数 == 0)    {        total = (total + ways[v]) % MOD;    }}コンソール.書く(total);