{ }codelper
全探索桁そろえP0321

すり減ったキーから絞り込む暗証番号

玄関の錠の暗証番号は数字 L 桁で、0 で始まることもある。
キーパッドを調べると、数字キーのうち k 個がすり減っている。
暗証番号はすり減ったキーの数字だけでできていて、すり減ったキーの数字はどれも 1 回以上使われている。
この錠は同じ数字を続けて押せないようになっているので、暗証番号の隣り合う 2 桁の数字は互いに異なる。
持ち主は、暗証番号の各桁の数字をすべて足すと S になることだけを覚えている。
これらの条件をすべて満たす暗証番号がいくつあるかを 1 行目に出力せよ。
1 つ以上あれば、そのうち数として最も小さいものを 2 行目に、最も大きいものを 3 行目に出力する。
暗証番号は先頭の 0 を省かず、L 桁のまま書く。
条件に合う暗証番号が 1 つだけなら、2 行目と 3 行目に同じものを書く。
1 つもなければ 1 行目に 0 だけを出力する。

入力
1 行目に L、k、S が空白 1 つずつで区切って与えられる。
2 行目にすり減ったキーの数字 k 個が空白 1 つずつで区切って与えられる。
数字は 0 以上 9 以下で互いに異なり、順番は決まっていない。
1 ≤ L ≤ 5、1 ≤ k ≤ 10、0 ≤ S ≤ 45
出力
1 行目に条件に合う暗証番号の数。
1 つ以上あれば 2 行目に最も小さい暗証番号、3 行目に最も大きい暗証番号(どちらも L 桁)
例 1 · stdin
4 2 14
0 7
stdout
2
0707
7070
例 2 · stdin
3 2 3
1 2
stdout
0
main.py
入力(stdin)

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

$ 準備完了

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

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

条件を式ひとつで解きにくいときは、候補をすべて作って 1 つずつ調べればよいです。
ここでは 00…0 から 99…9 までの 10^L 個で、L は 5 以下なので多くても 10 万個です。
落とし穴その一:暗証番号は数ではなく、数字を L 個並べたものです。
ループを 10^(L−1) から始めると、最初の例の 0707 のように 0 で始まる答えをまるごと見落とします。
落とし穴その二:桁を while (x > 0) で取り出すと、先頭の 0 を見ないまま終わります。
二つ目の例で 12(= 012)の 1 と 2 だけを見て、すり減ったキー 1、2 に合うと数えてしまうので、必ず L 回取り出します。
落とし穴その三:「すり減ったキーだけを使う」と「すり減ったキーをすべて使う」は別の条件なので、両方調べます。
すり減ったキーが 1、2、3 なのに 121 を数えると間違いです。
Python は文字の集合を比べる set(pin) != worn 一回で両方を見ます。
落とし穴その四:見つけた答えを数のまま持っていてそのまま出力すると、0707 が 707 になります。
前を 0 で埋める書き方は言語ごとに違います:Python f"{code:0{length}d}"、JavaScript・TypeScript String(code).padStart(length, "0")、Java String.format("%0" + length + "d", code)、C# code.ToString("D" + length)、C printf("%0*d", length, code)(幅を * の位置の引数で渡します)、C++ std::setfill('0') << std::setw(length)(setw は直後の値 1 つにしか効かないので値ごとに書きます)、Craykon ("" + code).前埋め(length, "0")。

Pythonmain.py · 22 行
length, k, target = map(int, input().split())worn = set(input().split())  # 닳은 키를 글자 집합으로: {"0", "7"} count = 0first = last = ""for code in range(10 ** length):  # 0부터: 0으로 시작하는 비밀번호도 있다    pin = f"{code:0{length}d}"  # 앞을 0으로 채워 length자리로    if set(pin) != worn:  # 닳은 키만, 그리고 빠짐없이        continue    if any(a == b for a, b in zip(pin, pin[1:])):  # 이웃한 자리가 같으면 안 된다        continue    if sum(map(int, pin)) != target:        continue    count += 1    if count == 1:        first = pin    last = pin  # 작은 수부터 보므로 마지막에 본 것이 가장 크다 print(count)if count > 0:    print(first)    print(last)
JavaScriptmain.js · 33 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [length, , target] = lines[0].trim().split(/\s+/).map(Number);const worn = lines[1].trim().split(/\s+/).map(Number); // 비밀번호 한 개가 세 조건을 모두 지키는지 본다function fits(pin) {  const used = new Set(pin); // 문자열을 넣으면 글자 하나하나가 들어간다  if (used.size !== worn.length || !worn.every((d) => used.has(String(d)))) return false;  let sum = 0;  for (let i = 0; i < pin.length; i++) {    if (i > 0 && pin[i] === pin[i - 1]) return false; // 이웃한 자리가 같으면 안 된다    sum += Number(pin[i]);  }  return sum === target;} let count = 0;let first = "";let last = "";// 0부터: 0으로 시작하는 비밀번호도 있다for (let code = 0; code < 10 ** length; code++) {  const pin = String(code).padStart(length, "0"); // 앞을 0으로 채워 length자리로  if (!fits(pin)) continue;  count++;  if (count === 1) first = pin;  last = pin; // 작은 수부터 보므로 마지막에 본 것이 가장 크다} console.log(count);if (count > 0) {  console.log(first);  console.log(last);}
TypeScriptmain.ts · 35 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const [length, , target] = lines[0].trim().split(/\s+/).map(Number);const worn: number[] = lines[1].trim().split(/\s+/).map(Number); // 비밀번호 한 개가 세 조건을 모두 지키는지 본다function fits(pin: string): boolean {  const used = new Set<string>(pin); // 문자열을 넣으면 글자 하나하나가 들어간다  if (used.size !== worn.length || !worn.every((d) => used.has(String(d)))) return false;  let sum = 0;  for (let i = 0; i < pin.length; i++) {    if (i > 0 && pin[i] === pin[i - 1]) return false; // 이웃한 자리가 같으면 안 된다    sum += Number(pin[i]);  }  return sum === target;} let count = 0;let first = "";let last = "";// 0부터: 0으로 시작하는 비밀번호도 있다for (let code = 0; code < 10 ** length; code++) {  const pin: string = String(code).padStart(length, "0"); // 앞을 0으로 채워 length자리로  if (!fits(pin)) continue;  count++;  if (count === 1) first = pin;  last = pin; // 작은 수부터 보므로 마지막에 본 것이 가장 크다} console.log(count);if (count > 0) {  console.log(first);  console.log(last);}
JavaMain.java · 49 行
import java.util.*; public class Main {    static int length, target;    static boolean[] worn = new boolean[10];     // 비밀번호 한 개가 세 조건을 모두 지키는지 본다    static boolean fits(int code) {        boolean[] used = new boolean[10];        int sum = 0, prev = -1, x = code;        // while (x > 0)이 아니라 꼭 length번: 앞자리의 0도 자리다        for (int i = 0; i < length; i++) {            int d = x % 10;            x /= 10;            if (!worn[d] || d == prev) return false; // 닳지 않은 키이거나 이웃과 같다            used[d] = true;            sum += d;            prev = d;        }        for (int d = 0; d < 10; d++)            if (worn[d] && !used[d]) return false; // 닳았는데 쓰지 않은 키        return sum == target;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        length = sc.nextInt();        int k = sc.nextInt();        target = sc.nextInt();        for (int i = 0; i < k; i++) worn[sc.nextInt()] = true;         int limit = 1;        for (int i = 0; i < length; i++) limit *= 10;        int count = 0, first = -1, last = -1;        for (int code = 0; code < limit; code++) { // 0부터: 0으로 시작하는 비밀번호도 있다            if (!fits(code)) continue;            count++;            if (first < 0) first = code;            last = code;        }         System.out.println(count);        if (count > 0) {            String format = "%0" + length + "d"; // 앞을 0으로 채워 length자리로            System.out.println(String.format(format, first));            System.out.println(String.format(format, last));        }    }}
C#Main.cs · 56 行
using System;using System.Linq; class Program{    static int length, target;    static bool[] worn = new bool[10];     // 비밀번호 한 개가 세 조건을 모두 지키는지 본다    static bool Fits(int code)    {        var used = new bool[10];        int sum = 0, prev = -1, x = code;        // while (x > 0)이 아니라 꼭 length번: 앞자리의 0도 자리다        for (int i = 0; i < length; i++)        {            int d = x % 10;            x /= 10;            if (!worn[d] || d == prev) return false; // 닳지 않은 키이거나 이웃과 같다            used[d] = true;            sum += d;            prev = d;        }        for (int d = 0; d < 10; d++)            if (worn[d] && !used[d]) return false; // 닳았는데 쓰지 않은 키        return sum == target;    }     static void Main()    {        int[] head = Console.ReadLine()!.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        length = head[0];        target = head[2];        foreach (string s in Console.ReadLine()!.Split(' ', StringSplitOptions.RemoveEmptyEntries))            worn[int.Parse(s)] = true;         int limit = 1;        for (int i = 0; i < length; i++) limit *= 10;        int count = 0, first = -1, last = -1;        for (int code = 0; code < limit; code++) // 0부터: 0으로 시작하는 비밀번호도 있다        {            if (!Fits(code)) continue;            count++;            if (first < 0) first = code;            last = code;        }         Console.WriteLine(count);        if (count > 0)        {            string format = "D" + length; // "D4"는 앞을 0으로 채운 4자리            Console.WriteLine(first.ToString(format));            Console.WriteLine(last.ToString(format));        }    }}
C++main.cpp · 49 行
#include <array>#include <iomanip>#include <iostream> int length, target;std::array<bool, 10> worn{}; // 비밀번호 한 개가 세 조건을 모두 지키는지 본다bool fits(int code) {    std::array<bool, 10> used{};    int sum = 0, prev = -1, x = code;    // while (x > 0)이 아니라 꼭 length번: 앞자리의 0도 자리다    for (int i = 0; i < length; i++) {        int d = x % 10;        x /= 10;        if (!worn[d] || d == prev) return false; // 닳지 않은 키이거나 이웃과 같다        used[d] = true;        sum += d;        prev = d;    }    return used == worn && sum == target; // 닳은 키를 빠짐없이 썼는지는 배열끼리 견준다} int main() {    int k;    std::cin >> length >> k >> target;    for (int i = 0; i < k; i++) {        int d;        std::cin >> d;        worn[d] = true;    }     int limit = 1;    for (int i = 0; i < length; i++) limit *= 10;    int count = 0, first = -1, last = -1;    for (int code = 0; code < limit; code++) { // 0부터: 0으로 시작하는 비밀번호도 있다        if (!fits(code)) continue;        count++;        if (first < 0) first = code;        last = code;    }     std::cout << count << '\n';    if (count > 0) {        // setfill은 한 번 정하면 계속 가지만 setw는 바로 다음 값 하나에만 듣는다        std::cout << std::setfill('0') << std::setw(length) << first << '\n'                  << std::setw(length) << last << '\n';    }}
Cmain.c · 51 行
#include <stdbool.h>#include <stdio.h> static int length, target;static bool worn[10]; /* 비밀번호 한 개가 세 조건을 모두 지키는지 본다 */static bool fits(int code) {    bool used[10] = {false};    int sum = 0, prev = -1, x = code;    /* while (x > 0)이 아니라 꼭 length번: 앞자리의 0도 자리다 */    for (int i = 0; i < length; i++) {        int d = x % 10;        x /= 10;        if (!worn[d] || d == prev) return false; /* 닳지 않은 키이거나 이웃과 같다 */        used[d] = true;        sum += d;        prev = d;    }    for (int d = 0; d < 10; d++)        if (worn[d] && !used[d]) return false; /* 닳았는데 쓰지 않은 키 */    return sum == target;} int main(void) {    int k;    if (scanf("%d %d %d", &length, &k, &target) != 3) return 0;    for (int i = 0; i < k; i++) {        int d;        if (scanf("%d", &d) != 1) return 0;        worn[d] = true;    }     int limit = 1;    for (int i = 0; i < length; i++) limit *= 10;    int count = 0, first = -1, last = -1;    for (int code = 0; code < limit; code++) { /* 0부터: 0으로 시작하는 비밀번호도 있다 */        if (!fits(code)) continue;        count++;        if (first < 0) first = code;        last = code;    }     printf("%d\n", count);    if (count > 0) {        /* %0*d: 너비를 인수로 받아 앞을 0으로 채운다 */        printf("%0*d\n", length, first);        printf("%0*d\n", length, last);    }    return 0;}
Craykonmain.cray · 68 行
// 비밀번호 한 개가 세 조건을 모두 지키는지 본다맞나(code, length, target, worn){    used = 新リスト(10, 偽);    sum = 0;    prev = -1;    x = code;    // 동안 (x > 0)이 아니라 꼭 length번: 앞자리의 0도 자리다    繰り返す (i = 0; i < length; i++)    {        d = x % 10;        x = 数学.商(x, 10);        もし (!worn[d] || d == prev)        {            返す 偽; // 닳지 않은 키이거나 이웃과 같다        }        used[d] = 真;        sum += d;        prev = d;    }    繰り返す (d = 0; d < 10; d++)    {        もし (worn[d] && !used[d])        {            返す 偽; // 닳았는데 쓰지 않은 키        }    }    返す sum == target;} head = コンソール.数読み();length = head[0];target = head[2];worn = 新リスト(10, 偽);それぞれ (d の中 コンソール.数読み()){    worn[d] = 真;} limit = 1;繰り返す (i = 0; i < length; i++){    limit *= 10;}count = 0;first = -1;last = -1;// 0부터: 0으로 시작하는 비밀번호도 있다繰り返す (code = 0; code < limit; code++){    もし (맞나(code, length, target, worn))    {        count += 1;        もし (first < 0)        {            first = code;        }        last = code;    }} コンソール.書く(count);もし (count > 0){    // 수를 문자열로 바꾼 다음 앞을 0으로 채워 length자리로    コンソール.書く(("" + first).前埋め(length, "0"));    コンソール.書く(("" + last).前埋め(length, "0"));}