{ }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);