{ }codelper
全探索P0399

タイルの壁と角の数字

壁にタイルが R 行、一行に C 枚ずつ貼ってある。
タイルは濃いタイル(#)か薄いタイル(.)だが、何枚かは隠れていて色がわからない(?)。
上下に隣り合う二つの行と左右に隣り合う二つの列が交わるところでは四枚のタイルが一点で出会い、その点に数字が一つ書いてある。
数字は、その点で出会う四枚のタイルのうち濃いタイルの数だ。
隠れたタイルごとに色を決めて、すべての数字が合うようにしたい。
見えているタイルの色は変えられない。
一行目に R と C が与えられる。
続く R 行に、壁が一行に C 文字ずつ与えられる。
続く R - 1 行に、数字が一行に C - 1 個ずつ与えられる。
このうち i 行目の j 番目の数字は、壁の i 行目と i + 1 行目、j 列目と j + 1 列目にある四枚のタイルが出会う点の数字だ。
まず、色の決め方が全部で何通りあるかを出力する。
決め方が一つでもあれば、そうして作れる壁のうち最も先の壁も出力する。
二つの壁を一行目から順に、行の中では左のタイルから比べて、初めて色が違う場所で薄いタイルを持つ壁が先の壁だ。

入力
2 ≤ R, C ≤ 10。
壁の文字は #、.、?
のどれかで、文字の間に空白はない。
数字は 0 以上 4 以下の整数で、空白一つで区切って与えられる
出力
一行目に決め方の数。
決め方があれば、続けて R 行に最も先の壁(# と .
だけで)
例 1 · stdin
3 4
???.
?#.?
????
2 2 2
2 2 2
stdout
3
..#.
##.#
..#.
例 2 · stdin
2 2
#?
?.
4
stdout
0
main.py
入力(stdin)

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

$ 準備完了

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

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

隠れたタイルを一枚ずつ全部決めてから数字を確かめると、タイルが 100 枚のとき 2^100 通りになり、終わりません。
しかし、ある行の最初のタイルまで決まれば、その行の残りのタイルには選ぶものがありません。
左上の点の数字から、すでに決まった三枚のタイルを引けばそのタイルが出るからです。
だから試すのは、一行目のタイル C 枚と、二行目からの最初のタイル R - 1 枚だけです。
多くても 2^19 通りで、数字に合わなくなった瞬間にその枝を捨てるのでずっと少なくなります。
解法はどれも再帰関数で一行目を一枚ずつ選び、そのあと行ごとに最初のタイルを選んで残りを埋めます。
薄いタイルを先に試すので、最初に見つかった壁が最も先の壁です。
作りかけの壁は、Python はリストのリスト、JS・TS は配列の配列、Java は int[][]、C# は int[,]、C は int grid[10][10]、C++ は std::vector<std::vector<int>>、Craykon は 新リスト(rows, 新リスト(cols, 0)) に置きます。
決め方の数を数える変数は、Python では内側の関数が変えるのに nonlocal が必要です。
TS は関数の中で変わった best を最初に入れた null としか知らないので、if (best !== null) の中で never と見て型エラー(TS2488)を出します。
そこで best as string[] | null と型を知らせ直します。
罠その一:合う壁を文字列として集めて最も小さいものを選んではいけません。
文字コードでは #(35)が .(46)より前なので、一つ目の例で #.#. から始まる壁が選ばれます。
罠その二:数字で決まったタイルも、すでに見えているタイルと同じでなければなりません。
確かめないと一つ目の例の決め方が 3 ではなく 11 になります。
罠その三:数字から引いて残った値は 0 か 1 でなければなりません。
負の値だけをはじいて 1 より大きい値をそのままにすると、一つ目の例が 4 になります。
罠その四:一行目を決めるだけですべてが決まるわけではありません。
二行目からも最初のタイルは選ばなければなりません。
薄いタイルにだけしておくと一つ目の例が 1 になります。
罠その五:Craykon で隠れたタイルを一枚ずつ全部決めてみると、大きい壁で「時間がかかりすぎます」という知らせとともに止まります。

Pythonmain.py · 58 行
import sys  def main():    data = sys.stdin.read().split()    rows, cols = int(data[0]), int(data[1])    # known[i][j]: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)    known = [[-1 if ch == "?" else int(ch == "#") for ch in data[2 + i]] for i in range(rows)]    pos = 2 + rows    hints = [[int(data[pos + i * (cols - 1) + j]) for j in range(cols - 1)] for i in range(rows - 1)]     grid = [[0] * cols for _ in range(rows)]    count = 0    best = None     def fill_rest(i):        """줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 False"""        for j in range(1, cols):            v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1]            if v < 0 or v > 1:                      # 타일은 짙거나 옅을 뿐이다                return False            if known[i][j] != -1 and known[i][j] != v:   # 이미 보이는 타일과 달라도 안 된다                return False            grid[i][j] = v        return True     def later_rows(i):        nonlocal count, best        if i == rows:            count += 1            if best is None:                        # 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다                best = ["".join(".#"[v] for v in row) for row in grid]            return        for v in (0, 1):                            # 둘째 줄부터는 첫 타일만 고른다            if known[i][0] != -1 and known[i][0] != v:                continue            grid[i][0] = v            if fill_rest(i):                later_rows(i + 1)     def first_row(j):        if j == cols:            later_rows(1)            return        for v in (0, 1):                            # 첫 줄은 타일마다 고른다            if known[0][j] != -1 and known[0][j] != v:                continue            grid[0][j] = v            first_row(j + 1)     first_row(0)    out = [str(count)]    if best is not None:        out += best    print("\n".join(out))  main()
JavaScriptmain.js · 59 行
const data = require("fs").readFileSync(0, "utf8").split(/\s+/).filter(Boolean);const rows = Number(data[0]), cols = Number(data[1]);// known[i][j]: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)const known = [];for (let i = 0; i < rows; i++) {  known.push([...data[2 + i]].map((ch) => (ch === "?" ? -1 : ch === "#" ? 1 : 0)));}const hints = [];let pos = 2 + rows;for (let i = 0; i < rows - 1; i++) {  hints.push(data.slice(pos, pos + cols - 1).map(Number));  pos += cols - 1;} const grid = known.map((row) => row.map(() => 0));let count = 0;let best = null; // 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 falsefunction fillRest(i) {  for (let j = 1; j < cols; j++) {    const v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1];    if (v < 0 || v > 1) return false;                           // 타일은 짙거나 옅을 뿐이다    if (known[i][j] !== -1 && known[i][j] !== v) return false;  // 이미 보이는 타일과 달라도 안 된다    grid[i][j] = v;  }  return true;} function laterRows(i) {  if (i === rows) {    count++;    // 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다    if (best === null) best = grid.map((row) => row.map((v) => ".#"[v]).join(""));    return;  }  for (let v = 0; v <= 1; v++) {            // 둘째 줄부터는 첫 타일만 고른다    if (known[i][0] !== -1 && known[i][0] !== v) continue;    grid[i][0] = v;    if (fillRest(i)) laterRows(i + 1);  }} function firstRow(j) {  if (j === cols) {    laterRows(1);    return;  }  for (let v = 0; v <= 1; v++) {            // 첫 줄은 타일마다 고른다    if (known[0][j] !== -1 && known[0][j] !== v) continue;    grid[0][j] = v;    firstRow(j + 1);  }} firstRow(0);const out = [String(count)];if (best !== null) out.push(...best);console.log(out.join("\n"));
TypeScriptmain.ts · 62 行
import { readFileSync } from "node:fs";const data: string[] = readFileSync(0, "utf8").split(/\s+/).filter(Boolean);const rows = Number(data[0]), cols = Number(data[1]);// known[i][j]: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)const known: number[][] = [];for (let i = 0; i < rows; i++) {  known.push([...data[2 + i]].map((ch) => (ch === "?" ? -1 : ch === "#" ? 1 : 0)));}const hints: number[][] = [];let pos = 2 + rows;for (let i = 0; i < rows - 1; i++) {  hints.push(data.slice(pos, pos + cols - 1).map(Number));  pos += cols - 1;} const grid: number[][] = known.map((row) => row.map(() => 0));let count = 0;let best: string[] | null = null; // 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 falsefunction fillRest(i: number): boolean {  for (let j = 1; j < cols; j++) {    const v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1];    if (v < 0 || v > 1) return false;                           // 타일은 짙거나 옅을 뿐이다    if (known[i][j] !== -1 && known[i][j] !== v) return false;  // 이미 보이는 타일과 달라도 안 된다    grid[i][j] = v;  }  return true;} function laterRows(i: number): void {  if (i === rows) {    count++;    // 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다    if (best === null) best = grid.map((row) => row.map((v) => ".#"[v]).join(""));    return;  }  for (let v = 0; v <= 1; v++) {            // 둘째 줄부터는 첫 타일만 고른다    if (known[i][0] !== -1 && known[i][0] !== v) continue;    grid[i][0] = v;    if (fillRest(i)) laterRows(i + 1);  }} function firstRow(j: number): void {  if (j === cols) {    laterRows(1);    return;  }  for (let v = 0; v <= 1; v++) {            // 첫 줄은 타일마다 고른다    if (known[0][j] !== -1 && known[0][j] !== v) continue;    grid[0][j] = v;    firstRow(j + 1);  }} firstRow(0);const out: string[] = [String(count)];// 함수 안에서 바꾼 best를 TS는 여전히 null로만 알고 있어서, 타입을 다시 알려 준다const found = best as string[] | null;if (found !== null) out.push(...found);console.log(out.join("\n"));
JavaMain.java · 75 行
import java.io.*;import java.util.*; public class Main {    static int rows, cols, count = 0;    static int[][] known, hints, grid;   // known: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)    static String[] best = null;     // 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 false    static boolean fillRest(int i) {        for (int j = 1; j < cols; j++) {            int v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1];            if (v < 0 || v > 1) return false;                           // 타일은 짙거나 옅을 뿐이다            if (known[i][j] != -1 && known[i][j] != v) return false;    // 이미 보이는 타일과 달라도 안 된다            grid[i][j] = v;        }        return true;    }     static void laterRows(int i) {        if (i == rows) {            count++;            if (best == null) {   // 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다                best = new String[rows];                for (int r = 0; r < rows; r++) {                    StringBuilder sb = new StringBuilder();                    for (int c = 0; c < cols; c++) sb.append(grid[r][c] == 1 ? '#' : '.');                    best[r] = sb.toString();                }            }            return;        }        for (int v = 0; v <= 1; v++) {            // 둘째 줄부터는 첫 타일만 고른다            if (known[i][0] != -1 && known[i][0] != v) continue;            grid[i][0] = v;            if (fillRest(i)) laterRows(i + 1);        }    }     static void firstRow(int j) {        if (j == cols) {            laterRows(1);            return;        }        for (int v = 0; v <= 1; v++) {            // 첫 줄은 타일마다 고른다            if (known[0][j] != -1 && known[0][j] != v) continue;            grid[0][j] = v;            firstRow(j + 1);        }    }     public static void main(String[] args) {        Scanner in = new Scanner(System.in);        rows = in.nextInt();        cols = in.nextInt();        known = new int[rows][cols];        grid = new int[rows][cols];        for (int i = 0; i < rows; i++) {            String line = in.next();            for (int j = 0; j < cols; j++) {                char ch = line.charAt(j);                known[i][j] = ch == '?' ? -1 : ch == '#' ? 1 : 0;            }        }        hints = new int[rows - 1][cols - 1];        for (int i = 0; i < rows - 1; i++)            for (int j = 0; j < cols - 1; j++) hints[i][j] = in.nextInt();         firstRow(0);        StringBuilder out = new StringBuilder();        out.append(count).append('\n');        if (best != null) for (String line : best) out.append(line).append('\n');        System.out.print(out);    }}
C#Main.cs · 87 行
using System;using System.Text; class Program{    static int rows, cols, count = 0;    static int[,] known, hints, grid;   // known: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)    static string[] best = null;     // 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 false    static bool FillRest(int i)    {        for (int j = 1; j < cols; j++)        {            int v = hints[i - 1, j - 1] - grid[i - 1, j - 1] - grid[i - 1, j] - grid[i, j - 1];            if (v < 0 || v > 1) return false;                           // 타일은 짙거나 옅을 뿐이다            if (known[i, j] != -1 && known[i, j] != v) return false;    // 이미 보이는 타일과 달라도 안 된다            grid[i, j] = v;        }        return true;    }     static void LaterRows(int i)    {        if (i == rows)        {            count++;            if (best == null)   // 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다            {                best = new string[rows];                for (int r = 0; r < rows; r++)                {                    var sb = new StringBuilder();                    for (int c = 0; c < cols; c++) sb.Append(grid[r, c] == 1 ? '#' : '.');                    best[r] = sb.ToString();                }            }            return;        }        for (int v = 0; v <= 1; v++)              // 둘째 줄부터는 첫 타일만 고른다        {            if (known[i, 0] != -1 && known[i, 0] != v) continue;            grid[i, 0] = v;            if (FillRest(i)) LaterRows(i + 1);        }    }     static void FirstRow(int j)    {        if (j == cols)        {            LaterRows(1);            return;        }        for (int v = 0; v <= 1; v++)              // 첫 줄은 타일마다 고른다        {            if (known[0, j] != -1 && known[0, j] != v) continue;            grid[0, j] = v;            FirstRow(j + 1);        }    }     static void Main()    {        string[] tokens = Console.In.ReadToEnd().Split((char[])null, StringSplitOptions.RemoveEmptyEntries);        rows = int.Parse(tokens[0]);        cols = int.Parse(tokens[1]);        known = new int[rows, cols];        grid = new int[rows, cols];        for (int i = 0; i < rows; i++)            for (int j = 0; j < cols; j++)            {                char ch = tokens[2 + i][j];                known[i, j] = ch == '?' ? -1 : ch == '#' ? 1 : 0;            }        hints = new int[rows - 1, cols - 1];        int pos = 2 + rows;        for (int i = 0; i < rows - 1; i++)            for (int j = 0; j < cols - 1; j++) hints[i, j] = int.Parse(tokens[pos++]);         FirstRow(0);        var output = new StringBuilder();        output.Append(count).Append('\n');        if (best != null) foreach (string line in best) output.Append(line).Append('\n');        Console.Write(output);    }}
C++main.cpp · 67 行
#include <iostream>#include <string>#include <vector> int rows, cols, count = 0;std::vector<std::vector<int>> known, hints, grid;   // known: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)std::vector<std::string> best; // 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 falsebool fillRest(int i) {    for (int j = 1; j < cols; j++) {        int v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1];        if (v < 0 || v > 1) return false;                           // 타일은 짙거나 옅을 뿐이다        if (known[i][j] != -1 && known[i][j] != v) return false;    // 이미 보이는 타일과 달라도 안 된다        grid[i][j] = v;    }    return true;} void laterRows(int i) {    if (i == rows) {        count++;        if (best.empty()) {   // 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다            for (const auto& row : grid) {                std::string line;                for (int v : row) line += v ? '#' : '.';                best.push_back(line);            }        }        return;    }    for (int v = 0; v <= 1; v++) {            // 둘째 줄부터는 첫 타일만 고른다        if (known[i][0] != -1 && known[i][0] != v) continue;        grid[i][0] = v;        if (fillRest(i)) laterRows(i + 1);    }} void firstRow(int j) {    if (j == cols) {        laterRows(1);        return;    }    for (int v = 0; v <= 1; v++) {            // 첫 줄은 타일마다 고른다        if (known[0][j] != -1 && known[0][j] != v) continue;        grid[0][j] = v;        firstRow(j + 1);    }} int main() {    std::cin >> rows >> cols;    known.assign(rows, std::vector<int>(cols));    grid.assign(rows, std::vector<int>(cols, 0));    for (int i = 0; i < rows; i++) {        std::string line;        std::cin >> line;        for (int j = 0; j < cols; j++) known[i][j] = line[j] == '?' ? -1 : line[j] == '#' ? 1 : 0;    }    hints.assign(rows - 1, std::vector<int>(cols - 1));    for (auto& row : hints)        for (int& h : row) std::cin >> h;     firstRow(0);    std::cout << count << '\n';    for (const auto& line : best) std::cout << line << '\n';}
Cmain.c · 69 行
#include <stdio.h>#include <string.h> #define MAX 10 static int rows, cols, count = 0, found = 0;static int known[MAX][MAX];   /* 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?) */static int hints[MAX][MAX], grid[MAX][MAX], best[MAX][MAX]; /* 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 0 */static int fill_rest(int i) {    for (int j = 1; j < cols; j++) {        int v = hints[i - 1][j - 1] - grid[i - 1][j - 1] - grid[i - 1][j] - grid[i][j - 1];        if (v < 0 || v > 1) return 0;                           /* 타일은 짙거나 옅을 뿐이다 */        if (known[i][j] != -1 && known[i][j] != v) return 0;    /* 이미 보이는 타일과 달라도 안 된다 */        grid[i][j] = v;    }    return 1;} static void later_rows(int i) {    if (i == rows) {        count++;        if (!found) {   /* 옅은 타일부터 해 보았으니 처음 찾은 벽이 가장 앞선다 */            found = 1;            memcpy(best, grid, sizeof grid);        }        return;    }    for (int v = 0; v <= 1; v++) {            /* 둘째 줄부터는 첫 타일만 고른다 */        if (known[i][0] != -1 && known[i][0] != v) continue;        grid[i][0] = v;        if (fill_rest(i)) later_rows(i + 1);    }} static void first_row(int j) {    if (j == cols) {        later_rows(1);        return;    }    for (int v = 0; v <= 1; v++) {            /* 첫 줄은 타일마다 고른다 */        if (known[0][j] != -1 && known[0][j] != v) continue;        grid[0][j] = v;        first_row(j + 1);    }} int main(void) {    char line[MAX + 2];    if (scanf("%d %d", &rows, &cols) != 2) return 0;    for (int i = 0; i < rows; i++) {        if (scanf("%11s", line) != 1) return 0;        for (int j = 0; j < cols; j++) known[i][j] = line[j] == '?' ? -1 : line[j] == '#' ? 1 : 0;    }    for (int i = 0; i < rows - 1; i++)        for (int j = 0; j < cols - 1; j++)            if (scanf("%d", &hints[i][j]) != 1) return 0;     first_row(0);    printf("%d\n", count);    if (found) {        for (int i = 0; i < rows; i++) {            for (int j = 0; j < cols; j++) putchar(best[i][j] ? '#' : '.');            putchar('\n');        }    }    return 0;}
Craykonmain.cray · 116 行
// 줄 i의 첫 타일이 정해지면 나머지 타일은 위쪽 숫자가 정해 준다. 맞지 않으면 거짓나머지채우기(i){    繰り返す (j = 1; j < 칸수; j++)    {        v = 숫자[i - 1][j - 1] - 벽[i - 1][j - 1] - 벽[i - 1][j] - 벽[i][j - 1];        もし (v < 0 || v > 1)        {            返す 偽;        }        もし (보임[i][j] != -1 && 보임[i][j] != v)        {            返す 偽;        }        벽[i][j] = v;    }    返す 真;} 아랫줄들(i){    もし (i == 줄수)    {        방법 += 1;        もし (앞선벽.個数 == 0)        {            それぞれ (줄 の中 벽)            {                글 = "";                それぞれ (v の中 줄)                {                    もし (v == 1)                    {                        글 = 글 + "#";                    }                    でなければ                    {                        글 = 글 + ".";                    }                }                앞선벽.追加(글);            }        }        返す;    }    繰り返す (v = 0; v <= 1; v++)    {        もし (보임[i][0] == -1 || 보임[i][0] == v)        {            벽[i][0] = v;            もし (나머지채우기(i))            {                아랫줄들(i + 1);            }        }    }} 첫줄(j){    もし (j == 칸수)    {        아랫줄들(1);        返す;    }    繰り返す (v = 0; v <= 1; v++)    {        もし (보임[0][j] == -1 || 보임[0][j] == v)        {            벽[0][j] = v;            첫줄(j + 1);        }    }} 크기 = コンソール.数読み();줄수 = 크기[0];칸수 = 크기[1];// 보임: 1 = 짙은 타일(#), 0 = 옅은 타일(.), -1 = 모르는 타일(?)보임 = [];繰り返す (i = 0; i < 줄수; i++){    글자들 = コンソール.行読み().空白除去();    한줄 = [];    繰り返す (j = 0; j < 칸수; j++)    {        もし (글자들[j] == "?")        {            한줄.追加(-1);        }        でなければ もし (글자들[j] == "#")        {            한줄.追加(1);        }        でなければ        {            한줄.追加(0);        }    }    보임.追加(한줄);}숫자 = [];繰り返す (i = 0; i < 줄수 - 1; i++){    숫자.追加(コンソール.数読み());}벽 = 新リスト(줄수, 新リスト(칸수, 0));방법 = 0;앞선벽 = []; 첫줄(0);コンソール.書く(방법);それぞれ (글 の中 앞선벽){    コンソール.書く(글);}