{ }codelper
暴力枚举补齐对齐P0321

从磨损的按键缩小密码范围

门锁的密码是 L 位数字,可以以 0 开头。
观察键盘,发现数字键中有 k 个已经磨损。
密码只由磨损按键上的数字组成,并且每个磨损按键的数字都至少用了一次。
这把锁不允许连续按同一个数字,所以密码中相邻两位的数字互不相同。
主人只记得密码各位数字加起来等于 S。
在第一行输出满足所有这些条件的密码有多少个。
如果至少有一个,在第二行输出其中按数值最小的,在第三行输出最大的。
密码要保留前面的 0,写满 L 位。
如果符合条件的密码只有一个,第二行和第三行写同一个。
如果一个也没有,只在第一行输出 0。

输入
第一行给出 L、k、S,用一个空格隔开。
第二行给出 k 个磨损按键的数字,用一个空格隔开。
数字在 0 到 9 之间,互不相同,顺序不固定。
1 ≤ L ≤ 5,1 ≤ k ≤ 10,0 ≤ S ≤ 45
输出
第一行是符合条件的密码个数。
至少有一个时,第二行是最小的密码,第三行是最大的密码(都是 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 种语言题解

当条件很难用一个式子解出来时,可以把候选全部生成出来逐个检查。
这里是从 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 只对紧接着的一个值有效,每个值都要再写一次),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"));}