{ }codelper
幅優先探索グラフ(隣接リスト)P0336

奇数分、偶数分にだけ開く橋

島が n 個あり、1 番から n 番まで番号が付いている。
島の間には橋が m 本あり、橋一本「a b k」は a 番の島と b 番の島を結ぶ種類 k の橋である。
橋はどちら向きにも渡れ、渡るのにちょうど 1 分かかる。
同じ二つの島を結ぶ橋が何本もあることがある。
旅人は 0 分に 1 番の島にいる。
t 分に橋を渡り始めると、t + 1 分に向こうの島に着く。
種類 0 の橋はいつでも渡り始められる。
種類 1 の橋は t が奇数のときだけ、種類 2 の橋は t が偶数のときだけ渡り始められる(0 は偶数である)。
渡り始めた橋は最後まで渡る。
島ごとに休憩所があるかどうかが与えられる。
sᵢ が 1 なら i 番の島に休憩所がある。
休憩所のある島では 1 分ずつ何回でも待てる。
休憩所のない島では待てないので、着いたその分に橋を渡り始めなければならない。
その分に渡り始められる橋がなければ、そこから先へは進めない。
0 分の 1 番の島でも同じである。
1 番の島に休憩所がなければ、0 分にすぐ橋を渡り始めなければならない。
同じ島や同じ橋を何度通ってもよい。
島ごとに、旅人がその島にいられる最も早い時刻(分)を求めよ。
1 番の島の値は 0 で、どうしても着けない島の値は -1 である。
一行目に着ける島の数を出力せよ。
1 番の島も数える。
二行目に 1 番から n 番の島までの値を順に空白一つで区切って出力せよ。

入力
一行目に n と m、二行目に s₁ … sₙ、続く m 行に各行 a、b、k が与えられる。
一行の数は空白一つで区切られる。
1 ≤ n ≤ 9,000、0 ≤ m ≤ 9,000、sᵢ は 0 か 1、1 ≤ a, b ≤ n、a ≠ b、k は 0、1、2 のどれか。
すべて整数である
出力
一行目に着ける島の数、二行目に島ごとの最も早い時刻(分)n 個。
着けなければ -1
例 1 · stdin
6 6
0 0 0 0 0 0
1 2 0
2 3 2
3 4 0
1 5 0
5 6 0
6 1 0
stdout
6
0 1 5 6 1 1
例 2 · stdin
3 2
0 1 0
1 2 0
2 3 2
stdout
3
0 1 3
main.py
入力(stdin)

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

$ 準備完了

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

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

幅優先探索(BFS)ですが、訪問済みとして記録するのは「島」ではなく「(島、分の偶奇)」です。
例 1 では 2 番の島に最初に着くのは 1 分ですが、奇数分なので種類 2 の橋は渡り始められません。
1 → 5 → 6 → 1 → 2 と回って 4 分(偶数)にもう一度 2 番の島に来て、やっと 3 番の島へ行けます。
落とし穴その一:島を一回ずつしか訪ねない普通の BFS は 2 番の島を二度とキューに入れないので、3 番と 4 番の島を -1 と出します。
状態は 2n 個で、答えは二つの偶奇のうち早いほうです。
落とし穴その二:橋は着く分ではなく出発する分の偶奇で決まります。
0 分に出て 1 分に着く橋は「偶数」側です。
落とし穴その三:待てるのは休憩所だけです。
どこでも待てるようにすると例 1 の 3 番の島が 3 になり、休憩所を忘れると例 2 の 3 番の島が -1 になります。
待つことは「同じ島、逆の偶奇」への 1 分の移動として入れればすみます。
キューは Python collections.deque(list.pop(0) は後ろの要素を全部ずらすので遅い)、Java ArrayDeque、C# Queue<T>、C++ std::queue を使い、JS・TS・C は配列と「先頭の番号」でします(JS の shift() も後ろをずらします)。
状態を 2 * v + p のように一つの数にまとめれば配列一つですみます。
Craykon のリストは 10,000 個までなので、2n 個の 新リスト(2 * n, -1) は n が 5,000 を超えると止まります。
そこで偶奇ごとにリストを分け(新リスト(n, 偽) を二つ)、同じ分に見つかった状態は偶奇がすべて同じであることを使って、キューの代わりに「この分の島のリスト」を一層ずつ渡します。

Pythonmain.py · 44 行
import sysfrom collections import deque  def main():    data = sys.stdin.buffer.read().split()    n, m = int(data[0]), int(data[1])    shelter = [0] + [int(x) for x in data[2:2 + n]]    bridges = [[] for _ in range(n + 1)]  # bridges[u] = (이웃 섬, 종류)들    pos = 2 + n    for _ in range(m):        a, b, k = int(data[pos]), int(data[pos + 1]), int(data[pos + 2])        pos += 3        bridges[a].append((b, k))        bridges[b].append((a, k))     # 상태 = (섬, 분의 홀짝). when[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때    when = [-1] * (2 * n + 2)    when[2 * 1 + 0] = 0    queue = deque([2])  # list.pop(0)은 뒤 칸을 모두 당겨서 느리다    while queue:        state = queue.popleft()        u, p = state // 2, state % 2        t = when[state] + 1        q = 1 - p        if shelter[u] and when[2 * u + q] == -1:  # 쉼터에서 1분 기다린다            when[2 * u + q] = t            queue.append(2 * u + q)        for v, k in bridges[u]:            # 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다            if (k == 0 or (k == 1) == (p == 1)) and when[2 * v + q] == -1:                when[2 * v + q] = t                queue.append(2 * v + q)     # 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽    first = []    for v in range(1, n + 1):        times = [x for x in (when[2 * v], when[2 * v + 1]) if x != -1]        first.append(min(times) if times else -1)    print(sum(1 for x in first if x != -1))    print(" ".join(map(str, first)))  main()
JavaScriptmain.js · 51 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const m = data[1];const shelter = [0];for (let v = 1; v <= n; v++) shelter.push(data[1 + v]); // bridges[u] = [이웃 섬, 종류]들(다리는 양쪽으로 건넌다)const bridges = Array.from({ length: n + 1 }, () => []);for (let i = 0; i < m; i++) {  const a = data[2 + n + 3 * i];  const b = data[3 + n + 3 * i];  const k = data[4 + n + 3 * i];  bridges[a].push([b, k]);  bridges[b].push([a, k]);} // 상태 = (섬, 분의 홀짝). time[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때const time = new Array(2 * n + 2).fill(-1);time[2 * 1 + 0] = 0;const queue = [2];// shift()는 뒤의 것을 모두 당겨서 느리다. 머리 번호 head를 옮긴다for (let head = 0; head < queue.length; head++) {  const u = queue[head] >> 1;  const p = queue[head] & 1;  const t = time[queue[head]];  const visit = (v) => {    const s = 2 * v + (1 - p);    if (time[s] === -1) {      time[s] = t + 1;      queue.push(s);    }  };  if (shelter[u] === 1) visit(u); // 쉼터에서 1분 기다린다  for (const [v, k] of bridges[u]) {    // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다    if (k === 0 || (k === 1) === (p === 1)) visit(v);  }} const first = [];let reached = 0;for (let v = 1; v <= n; v++) {  const even = time[2 * v];  const odd = time[2 * v + 1];  // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽  const best = even === -1 ? odd : odd === -1 ? even : Math.min(even, odd);  if (best !== -1) reached++;  first.push(best);}console.log(reached);console.log(first.join(" "));
TypeScriptmain.ts · 54 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const m = data[1];const shelter: number[] = [0];for (let v = 1; v <= n; v++) shelter.push(data[1 + v]); // bridges[u] = [이웃 섬, 종류]들(다리는 양쪽으로 건넌다)type Bridge = [to: number, kind: number];const bridges: Bridge[][] = Array.from({ length: n + 1 }, (): Bridge[] => []);for (let i = 0; i < m; i++) {  const a = data[2 + n + 3 * i];  const b = data[3 + n + 3 * i];  const k = data[4 + n + 3 * i];  bridges[a].push([b, k]);  bridges[b].push([a, k]);} // 상태 = (섬, 분의 홀짝). time[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때const time: number[] = new Array<number>(2 * n + 2).fill(-1);time[2 * 1 + 0] = 0;const queue: number[] = [2];// shift()는 뒤의 것을 모두 당겨서 느리다. 머리 번호 head를 옮긴다for (let head = 0; head < queue.length; head++) {  const u = queue[head] >> 1;  const p = queue[head] & 1;  const t = time[queue[head]];  const visit = (v: number): void => {    const s = 2 * v + (1 - p);    if (time[s] === -1) {      time[s] = t + 1;      queue.push(s);    }  };  if (shelter[u] === 1) visit(u); // 쉼터에서 1분 기다린다  for (const [v, k] of bridges[u]) {    // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다    if (k === 0 || (k === 1) === (p === 1)) visit(v);  }} const first: number[] = [];let reached = 0;for (let v = 1; v <= n; v++) {  const even = time[2 * v];  const odd = time[2 * v + 1];  // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽  const best = even === -1 ? odd : odd === -1 ? even : Math.min(even, odd);  if (best !== -1) reached++;  first.push(best);}console.log(reached);console.log(first.join(" "));
JavaMain.java · 67 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken();        int n = (int) st.nval;        st.nextToken();        int m = (int) st.nval;        boolean[] shelter = new boolean[n + 1];        for (int v = 1; v <= n; v++) {            st.nextToken();            shelter[v] = st.nval == 1;        }         // bridges.get(u) = {이웃 섬, 종류}들(다리는 양쪽으로 건넌다)        List<List<int[]>> bridges = new ArrayList<>();        for (int v = 0; v <= n; v++) bridges.add(new ArrayList<>());        for (int i = 0; i < m; i++) {            st.nextToken();            int a = (int) st.nval;            st.nextToken();            int b = (int) st.nval;            st.nextToken();            int k = (int) st.nval;            bridges.get(a).add(new int[] {b, k});            bridges.get(b).add(new int[] {a, k});        }         // 상태 = (섬, 분의 홀짝). time[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때        int[] time = new int[2 * n + 2];        Arrays.fill(time, -1);        time[2] = 0;        ArrayDeque<Integer> queue = new ArrayDeque<>();        queue.add(2);        while (!queue.isEmpty()) {            int state = queue.poll();            int u = state / 2, p = state % 2;            int t = time[state] + 1;            if (shelter[u] && time[2 * u + 1 - p] == -1) { // 쉼터에서 1분 기다린다                time[2 * u + 1 - p] = t;                queue.add(2 * u + 1 - p);            }            for (int[] bridge : bridges.get(u)) {                int v = bridge[0], k = bridge[1];                // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다                boolean open = k == 0 || (k == 1) == (p == 1);                if (open && time[2 * v + 1 - p] == -1) {                    time[2 * v + 1 - p] = t;                    queue.add(2 * v + 1 - p);                }            }        }         StringBuilder sb = new StringBuilder();        int reached = 0;        for (int v = 1; v <= n; v++) {            int even = time[2 * v], odd = time[2 * v + 1];            // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽            int best = even == -1 ? odd : odd == -1 ? even : Math.min(even, odd);            if (best != -1) reached++;            sb.append(best).append(v < n ? ' ' : '\n');        }        System.out.print(reached + "\n" + sb);    }}
C#Main.cs · 64 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), m = int.Parse(data[1]);        var shelter = new bool[n + 1];        for (int v = 1; v <= n; v++) shelter[v] = data[1 + v] == "1";         // bridges[u] = (이웃 섬, 종류)들(다리는 양쪽으로 건넌다)        var bridges = new List<(int To, int Kind)>[n + 1];        for (int v = 0; v <= n; v++) bridges[v] = new List<(int To, int Kind)>();        for (int i = 0; i < m; i++)        {            int a = int.Parse(data[2 + n + 3 * i]), b = int.Parse(data[3 + n + 3 * i]), k = int.Parse(data[4 + n + 3 * i]);            bridges[a].Add((b, k));            bridges[b].Add((a, k));        }         // 상태 = (섬, 분의 홀짝). time[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때        var time = new int[2 * n + 2];        Array.Fill(time, -1);        time[2] = 0;        var queue = new Queue<int>();        queue.Enqueue(2);        while (queue.Count > 0)        {            int state = queue.Dequeue();            int u = state / 2, p = state % 2;            int t = time[state] + 1;            if (shelter[u] && time[2 * u + 1 - p] == -1) // 쉼터에서 1분 기다린다            {                time[2 * u + 1 - p] = t;                queue.Enqueue(2 * u + 1 - p);            }            foreach (var (v, k) in bridges[u])            {                // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다                bool open = k == 0 || (k == 1) == (p == 1);                if (open && time[2 * v + 1 - p] == -1)                {                    time[2 * v + 1 - p] = t;                    queue.Enqueue(2 * v + 1 - p);                }            }        }         var sb = new StringBuilder();        int reached = 0;        for (int v = 1; v <= n; v++)        {            int even = time[2 * v], odd = time[2 * v + 1];            // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽            int best = even == -1 ? odd : odd == -1 ? even : Math.Min(even, odd);            if (best != -1) reached++;            sb.Append(best).Append(v < n ? ' ' : '\n');        }        Console.Write(reached + "\n" + sb);    }}
C++main.cpp · 60 行
#include <algorithm>#include <iostream>#include <queue>#include <utility>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, m;    cin >> n >> m;    vector<int> shelter(n + 1);    for (int v = 1; v <= n; v++) cin >> shelter[v];     // bridges[u] = {이웃 섬, 종류}들(다리는 양쪽으로 건넌다)    vector<vector<pair<int, int>>> bridges(n + 1);    for (int i = 0; i < m; i++) {        int a, b, k;        cin >> a >> b >> k;        bridges[a].push_back({b, k});        bridges[b].push_back({a, k});    }     // 상태 = (섬, 분의 홀짝). when[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때    vector<int> when(2 * n + 2, -1);    queue<int> q;    when[2] = 0;    q.push(2);    while (!q.empty()) {        int state = q.front();        q.pop();        int u = state / 2, p = state % 2;        int t = when[state] + 1;        if (shelter[u] && when[2 * u + 1 - p] == -1) { // 쉼터에서 1분 기다린다            when[2 * u + 1 - p] = t;            q.push(2 * u + 1 - p);        }        for (auto [v, k] : bridges[u]) {            // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다            bool open = k == 0 || (k == 1) == (p == 1);            if (open && when[2 * v + 1 - p] == -1) {                when[2 * v + 1 - p] = t;                q.push(2 * v + 1 - p);            }        }    }     vector<int> first(n + 1);    int reached = 0;    for (int v = 1; v <= n; v++) {        int even = when[2 * v], odd = when[2 * v + 1];        // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽        first[v] = even == -1 ? odd : odd == -1 ? even : min(even, odd);        if (first[v] != -1) reached++;    }    cout << reached << "\n";    for (int v = 1; v <= n; v++) cout << first[v] << (v < n ? ' ' : '\n');    return 0;}
Cmain.c · 65 行
#include <stdio.h>#include <stdlib.h> int main(void) {    int n, m;    if (scanf("%d %d", &n, &m) != 2) return 0;    int *shelter = malloc((n + 1) * sizeof(int));    for (int v = 1; v <= n; v++) scanf("%d", &shelter[v]);     // 인접 목록을 배열로: head[u]에서 시작해 next[e]로 따라가며 to[e], kind[e]를 본다    // 다리 하나가 양쪽으로 건너는 길 둘이 되므로 칸이 2m개 든다    int *head = malloc((n + 1) * sizeof(int));    int *next = malloc((2 * m + 1) * sizeof(int));    int *to = malloc((2 * m + 1) * sizeof(int));    int *kind = malloc((2 * m + 1) * sizeof(int));    for (int v = 0; v <= n; v++) head[v] = -1;    for (int i = 0; i < m; i++) {        int a, b, k;        scanf("%d %d %d", &a, &b, &k);        to[2 * i] = b; kind[2 * i] = k; next[2 * i] = head[a]; head[a] = 2 * i;        to[2 * i + 1] = a; kind[2 * i + 1] = k; next[2 * i + 1] = head[b]; head[b] = 2 * i + 1;    }     // 상태 = (섬, 분의 홀짝). time[2 * v + p] = v번 섬에 홀짝이 p인 분에 처음 있게 되는 때    int states = 2 * n + 2;    int *time = malloc(states * sizeof(int));    int *queue = malloc(states * sizeof(int)); // 상태마다 한 번만 들어간다    for (int s = 0; s < states; s++) time[s] = -1;    int qh = 0, qt = 0;    time[2] = 0;    queue[qt++] = 2;    while (qh < qt) {        int state = queue[qh++];        int u = state / 2, p = state % 2;        int t = time[state] + 1;        if (shelter[u] && time[2 * u + 1 - p] == -1) { // 쉼터에서 1분 기다린다            time[2 * u + 1 - p] = t;            queue[qt++] = 2 * u + 1 - p;        }        for (int e = head[u]; e != -1; e = next[e]) {            int v = to[e], k = kind[e];            // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다            int open = k == 0 || (k == 1) == (p == 1);            if (open && time[2 * v + 1 - p] == -1) {                time[2 * v + 1 - p] = t;                queue[qt++] = 2 * v + 1 - p;            }        }    }     int reached = 0;    for (int v = 1; v <= n; v++) {        if (time[2 * v] != -1 || time[2 * v + 1] != -1) reached++;    }    printf("%d\n", reached);    for (int v = 1; v <= n; v++) {        int even = time[2 * v], odd = time[2 * v + 1];        // 두 홀짝 가운데 닿은 쪽, 둘 다 닿았으면 이른 쪽        int best = even == -1 ? odd : odd == -1 ? even : (even < odd ? even : odd);        printf(v > 1 ? " %d" : "%d", best);    }    printf("\n");    free(shelter); free(head); free(next); free(to); free(kind); free(time); free(queue);    return 0;}
Craykonmain.cray · 65 行
첫줄 = コンソール.数読み();n = 첫줄[0];m = 첫줄[1];// 목록은 10,000칸까지라 섬 번호에서 1을 빼 0번부터 둔다shelter = コンソール.数読み(); // bridges[u] = [이웃 섬, 종류]들(다리는 양쪽으로 건넌다)bridges = [];繰り返す (v = 0; v < n; v++){    bridges.追加([]);}繰り返す (i = 0; i < m; i++){    줄 = コンソール.数読み();    bridges[줄[0] - 1].追加([줄[1] - 1, 줄[2]]);    bridges[줄[1] - 1].追加([줄[0] - 1, 줄[2]]);} // 상태 = (섬, 분의 홀짝). 상태가 2n개라 목록 하나(10,000칸)에 못 담으니 홀짝마다 목록을 따로 둔다// seen[p][v] = v번 섬에 홀짝이 p인 분에 있어 본 적이 있는가seen = [新リスト(n, 偽), 新リスト(n, 偽)];first = 新リスト(n, -1);seen[0][0] = 真;first[0] = 0;reached = 1;// t분에 새로 나온 상태는 홀짝이 모두 같으니, 큐 대신 '이번 분의 섬들'을 한 층씩 넘긴다frontier = [0];t = 0;間 (frontier.個数 > 0){    p = t % 2;    q = 1 - p;    next = [];    それぞれ (u の中 frontier)    {        もし (shelter[u] == 1 && !seen[q][u])        {            seen[q][u] = 真;            next.追加(u);        }        それぞれ (b の中 bridges[u])        {            v = b[0];            k = b[1];            // 떠나는 분이 홀수면 1번, 짝수면 2번 다리가 열려 있다            open = k == 0 || k == 1 && p == 1 || k == 2 && p == 0;            もし (open && !seen[q][v])            {                seen[q][v] = 真;                もし (first[v] == -1)                {                    first[v] = t + 1;                    reached++;                }                next.追加(v);            }        }    }    frontier = next;    t++;} コンソール.書く(reached);コンソール.書く(first);