{ }codelper
进制转换P0358

去掉忌讳数字的号码牌

有一栋楼不使用含有任何忌讳数字的号码。
号码牌是指不小于 1 的整数中,用十进制写出来时一次也不出现忌讳数字的数。
把号码牌从小到大排列,依次数作第 1 个、第 2 个、第 3 个、……。
例如忌讳数字只有 4 时,号码牌是 1, 2, 3, 5, 6, …, 13, 15, …,第 4 个号码牌是 5。
忌讳数字是 0 和 3, 4, 5, 6, 7, 8, 9 时,号码牌是 1, 2, 11, 12, 21, 22, 111, …。
命令有两种。
I k:输出第 k 个号码牌。
L x:输出号码牌 x 是第几个。

输入
第一行给出忌讳数字,从小到大连在一起,中间没有空格。
忌讳数字互不相同,有 1 ~ 8 个。
第二行给出命令数 q (1 ≤ q ≤ 1,000)。
接下来的 q 行每行一条命令,命令字母与 k 或 x 之间用一个空格隔开。
k 是整数,1 ≤ k ≤ 10^15。
x 是不含忌讳数字的号码牌,它的位次不超过 10^15。
x 最多可以有 50 位
输出
每条命令输出一行,按命令的顺序输出。
只写数字,不加逗号
样例 1 · stdin
4
6
I 3
I 4
I 9
I 13
L 50
I 81
stdout
3
5
10
15
36
100
样例 2 · stdin
03456789
7
I 2
I 3
I 6
I 7
L 2112
I 1000000000000000
L 2211122121222222121211211221112212111111111111111
stdout
2
11
22
111
24
2211122121222222121211211221112212111111111111112
999999999999999
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

如果可用的数字有 b 个,号码牌就是把 b 进制数每一位的值换成可用数字写出来的结果。
只去掉 4 时是 9 进制,位值 0 ~ 8 对应数字 0, 1, 2, 3, 5, 6, 7, 8, 9。
第一个例子的 L 50 位值是 4, 0,所以是 4 × 9 + 0 = 36。
陷阱一:如果去掉了 0,就不是普通的 b 进制了。
这时是位值为 1 ~ b 的“没有 0 的 b 进制”,余数为 0 时要用位值 b,先从 k 中减去 b 再除。
相当于向高位借了一个。
在只用 1 和 2 的第二个例子里,按普通 2 进制转换的话 I 2 会得到 21 而不是 2;不借位直接除的话会得到 12。
陷阱二:余数是从低位开始出来的。
不反转的话,第一个例子的 I 9 会得到 01 而不是 10。
陷阱三:号码牌从 1 开始。
即使这栋楼使用 0,0 也不是号码牌,所以直接把 k 转成 b 进制。
如果转换 k - 1,第一个例子的 I 4 会得到 3 而不是 5。
陷阱四:号码牌最多有 50 位,不能当作数来读。
JS 的 Number(x) 从大约 16 位起就会丢掉低位,第二个例子的最后一行会出错。
解答在八种语言里都把号码牌当作字符串读入,逐个字符处理。
陷阱五:位次最大到 10^15,放进 32 位的 int 会溢出。
在 Java 里用 int 累加的话,第二个例子的最后一行会变成 -1530494977。
解答在 C、C++ 中用 long long,在 Java、C# 中用 long。
JS、TS 的 number 和 Craykon 的数在 2^53 以内是精确的,所以 10^15 没问题;Python 的整数没有大小限制。
商在 Python 中是 k // b,JS、TS 是 Math.floor(k / b),Java、C#、C、C++ 是整数之间的 k / b,Craykon 是 数学.商(k, b)。
查一个数字对应第几个位值:Python allowed.index(ch),JS、TS、Java indexOf,C# IndexOf,C strchr,C++ find,Craykon 查找位置。

Pythonmain.py · 45 行
import sys  def main():    lines = sys.stdin.read().split("\n")    banned = lines[0].strip()    allowed = [d for d in "0123456789" if d not in banned]  # 쓰는 숫자, 작은 것부터    b = len(allowed)    has_zero = allowed[0] == "0"     def to_label(k):        digits = []        while k > 0:            r = k % b            if has_zero:                # 보통의 b진법: 나머지 0 ~ b-1이 그대로 자릿값이다                digits.append(allowed[r])                k //= b            else:                # 0이 없는 b진법: 자릿값은 1 ~ b. 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다                if r == 0:                    r = b                digits.append(allowed[r - 1])                k = (k - r) // b        return "".join(reversed(digits))  # 낮은 자리부터 나왔으니 뒤집는다     def to_index(label):        k = 0        for ch in label:            value = allowed.index(ch) if has_zero else allowed.index(ch) + 1            k = k * b + value        return k     q = int(lines[1])    out = []    for line in lines[2:2 + q]:        kind, arg = line.split()        if kind == "I":            out.append(to_label(int(arg)))        else:            out.append(str(to_index(arg)))  # 번호표는 수로 바꾸지 않고 문자열 그대로 본다    print("\n".join(out))  main()
JavaScriptmain.js · 40 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const banned = lines[0].trim();const allowed = [..."0123456789"].filter((d) => !banned.includes(d)); // 쓰는 숫자, 작은 것부터const b = allowed.length;const hasZero = allowed[0] === "0"; function toLabel(k) {  const digits = [];  while (k > 0) {    let r = k % b;    if (hasZero) {      // 보통의 b진법      digits.push(allowed[r]);      k = Math.floor(k / b);    } else {      // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다      if (r === 0) r = b;      digits.push(allowed[r - 1]);      k = (k - r) / b;    }  }  return digits.reverse().join("");} function toIndex(label) {  let k = 0;  for (const ch of label) {    k = k * b + allowed.indexOf(ch) + (hasZero ? 0 : 1);  }  return k;} const q = Number(lines[1]);const out = [];for (let i = 2; i < 2 + q; i++) {  const [kind, arg] = lines[i].split(" ");  // 차례 번호는 10^15 이하라 Number로 정확하다. 번호표는 50자리까지라 문자열 그대로 본다  out.push(kind === "I" ? toLabel(Number(arg)) : String(toIndex(arg)));}console.log(out.join("\n"));
TypeScriptmain.ts · 40 行
const lines: string[] = require("fs").readFileSync(0, "utf8").split("\n");const banned: string = lines[0].trim();const allowed: string[] = [..."0123456789"].filter((d) => !banned.includes(d)); // 쓰는 숫자, 작은 것부터const b: number = allowed.length;const hasZero: boolean = allowed[0] === "0"; function toLabel(k: number): string {  const digits: string[] = [];  while (k > 0) {    let r = k % b;    if (hasZero) {      // 보통의 b진법      digits.push(allowed[r]);      k = Math.floor(k / b);    } else {      // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다      if (r === 0) r = b;      digits.push(allowed[r - 1]);      k = (k - r) / b;    }  }  return digits.reverse().join("");} function toIndex(label: string): number {  let k = 0;  for (const ch of label) {    k = k * b + allowed.indexOf(ch) + (hasZero ? 0 : 1);  }  return k;} const q: number = Number(lines[1]);const out: string[] = [];for (let i = 2; i < 2 + q; i++) {  const [kind, arg] = lines[i].split(" ");  // 차례 번호는 10^15 이하라 number로 정확하다. 번호표는 50자리까지라 문자열 그대로 본다  out.push(kind === "I" ? toLabel(Number(arg)) : String(toIndex(arg)));}console.log(out.join("\n"));
JavaMain.java · 58 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    static String allowed; // 쓰는 숫자, 작은 것부터    static int b;    static boolean hasZero;     static String toLabel(long k) {        StringBuilder sb = new StringBuilder();        while (k > 0) {            int r = (int) (k % b);            if (hasZero) {                // 보통의 b진법                sb.append(allowed.charAt(r));                k /= b;            } else {                // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다                if (r == 0) r = b;                sb.append(allowed.charAt(r - 1));                k = (k - r) / b;            }        }        return sb.reverse().toString();    }     static long toIndex(String label) {        long k = 0; // 10^15까지라 int로는 모자란다        for (int i = 0; i < label.length(); i++) {            k = k * b + allowed.indexOf(label.charAt(i)) + (hasZero ? 0 : 1);        }        return k;    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String banned = br.readLine().trim();        StringBuilder a = new StringBuilder();        for (char d = '0'; d <= '9'; d++) {            if (banned.indexOf(d) < 0) a.append(d);        }        allowed = a.toString();        b = allowed.length();        hasZero = allowed.charAt(0) == '0';        int q = Integer.parseInt(br.readLine().trim());        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] parts = br.readLine().split(" ");            if (parts[0].equals("I")) {                out.append(toLabel(Long.parseLong(parts[1]))).append('\n');            } else {                out.append(toIndex(parts[1])).append('\n');            }        }        System.out.print(out);    }}
C#Main.cs · 64 行
using System;using System.Text; class Program{    static string allowed = ""; // 쓰는 숫자, 작은 것부터    static int b;    static bool hasZero;     static string ToLabel(long k)    {        var digits = new StringBuilder();        while (k > 0)        {            int r = (int)(k % b);            if (hasZero)            {                // 보통의 b진법                digits.Insert(0, allowed[r]);                k /= b;            }            else            {                // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다                if (r == 0) r = b;                digits.Insert(0, allowed[r - 1]);                k = (k - r) / b;            }        }        return digits.ToString();    }     static long ToIndex(string label)    {        long k = 0; // 10^15까지라 int로는 모자란다        foreach (char ch in label)        {            k = k * b + allowed.IndexOf(ch) + (hasZero ? 0 : 1);        }        return k;    }     static void Main()    {        string banned = Console.ReadLine().Trim();        var a = new StringBuilder();        for (char d = '0'; d <= '9'; d++)        {            if (banned.IndexOf(d) < 0) a.Append(d);        }        allowed = a.ToString();        b = allowed.Length;        hasZero = allowed[0] == '0';        int q = int.Parse(Console.ReadLine());        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] parts = Console.ReadLine().Split(' ');            if (parts[0] == "I") sb.Append(ToLabel(long.Parse(parts[1]))).Append('\n');            else sb.Append(ToIndex(parts[1])).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 57 行
#include <algorithm>#include <iostream>#include <string>using namespace std; string allowed; // 쓰는 숫자, 작은 것부터int b;bool hasZero; string toLabel(long long k) {    string digits;    while (k > 0) {        int r = static_cast<int>(k % b);        if (hasZero) {            // 보통의 b진법            digits += allowed[r];            k /= b;        } else {            // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다            if (r == 0) r = b;            digits += allowed[r - 1];            k = (k - r) / b;        }    }    reverse(digits.begin(), digits.end());    return digits;} long long toIndex(const string& label) {    long long k = 0; // 10^15까지라 int로는 모자란다    for (char ch : label) {        k = k * b + static_cast<long long>(allowed.find(ch)) + (hasZero ? 0 : 1);    }    return k;} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    string banned;    cin >> banned;    for (char d = '0'; d <= '9'; d++) {        if (banned.find(d) == string::npos) allowed += d;    }    b = static_cast<int>(allowed.size());    hasZero = allowed[0] == '0';    int q;    cin >> q;    for (int i = 0; i < q; i++) {        char kind;        string arg; // 번호표는 50자리까지라 수가 아닌 문자열로 읽는다        cin >> kind >> arg;        if (kind == 'I') cout << toLabel(stoll(arg)) << '\n';        else cout << toIndex(arg) << '\n';    }    return 0;}
Cmain.c · 63 行
#include <stdio.h>#include <string.h> static char allowed[11]; /* 쓰는 숫자, 작은 것부터 */static int b;static int has_zero; /* k번째 번호표를 out에 적는다 */static void to_label(long long k, char *out) {    char rev[64];    int len = 0;    while (k > 0) {        int r = (int)(k % b);        if (has_zero) {            /* 보통의 b진법 */            rev[len++] = allowed[r];            k /= b;        } else {            /* 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다 */            if (r == 0) r = b;            rev[len++] = allowed[r - 1];            k = (k - r) / b;        }    }    for (int i = 0; i < len; i++) out[i] = rev[len - 1 - i];    out[len] = '\0';} static long long to_index(const char *label) {    long long k = 0; /* 10^15까지라 int로는 모자란다 */    for (int i = 0; label[i] != '\0'; i++) {        int value = (int)(strchr(allowed, label[i]) - allowed);        k = k * b + value + (has_zero ? 0 : 1);    }    return k;} int main(void) {    char banned[16];    if (scanf("%15s", banned) != 1) return 0;    for (char d = '0'; d <= '9'; d++) {        if (strchr(banned, d) == NULL) allowed[b++] = d;    }    allowed[b] = '\0';    has_zero = allowed[0] == '0';    int q;    if (scanf("%d", &q) != 1) return 0;    for (int i = 0; i < q; i++) {        char kind;        char arg[64]; /* 번호표는 50자리까지라 수가 아닌 문자열로 읽는다 */        if (scanf(" %c %63s", &kind, arg) != 2) return 0;        if (kind == 'I') {            long long k;            char label[64];            sscanf(arg, "%lld", &k);            to_label(k, label);            printf("%s\n", label);        } else {            printf("%lld\n", to_index(arg));        }    }    return 0;}
Craykonmain.cray · 68 行
뺀숫자 = 控制台.读行().去空白();// 쓰는 숫자를 작은 것부터 이어 붙인 문자열쓰는숫자 = "";循环 (d = 0; d <= 9; d++){    如果 (!뺀숫자.包含(d + ""))    {        쓰는숫자 = 쓰는숫자 + d;    }}b = 쓰는숫자.长度;영있나 = 쓰는숫자.截取(0, 1) == "0"; 번호표로(k){    결과 = "";    当 (k > 0)    {        r = k % b;        如果 (영있나)        {            // 보통의 b진법            결과 = 쓰는숫자.截取(r, 1) + 결과;            k = 数学.商(k, b);        }        否则        {            // 0이 없는 b진법: 나머지 0은 자릿값 b로 치고 윗자리에서 하나를 빌린다            如果 (r == 0)            {                r = b;            }            결과 = 쓰는숫자.截取(r - 1, 1) + 결과;            k = 数学.商(k - r, b);        }    }    返回 결과;} 차례로(번호표){    k = 0;    循环 (i = 0; i < 번호표.长度; i++)    {        값 = 쓰는숫자.查找位置(번호표.截取(i, 1));        如果 (!영있나)        {            값 += 1;        }        k = k * b + 값;    }    返回 k;} q = 控制台.读行().转数字();循环 (i = 0; i < q; i++){    칸 = 控制台.读行().分割(" ");    如果 (칸[0] == "I")    {        控制台.输出(번호표로(칸[1].转数字()));    }    否则    {        // 번호표는 50자리까지라 수로 바꾸지 않고 문자열 그대로 본다        控制台.输出(차례로(칸[1]) + "");    }}