{ }codelper
進数変換P0358

避ける数字を除いた番号札

ある建物は、避ける数字が1つでも入った番号を使わない。
番号札とは、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, … である。
命令は2種類ある。
I k: k 番目の番号札を出力する。
L x: 番号札 x が何番目かを出力する。

入力
1行目に避ける数字が小さい順に、空白なしで続けて与えられる。
避ける数字は互いに異なり、1 ~ 8 個である。
2行目に命令の数 q (1 ≤ q ≤ 1,000) が与えられる。
続く q 行に命令が1行に1つずつ、命令の文字と k または x を空白1つで区切って与えられる。
k は整数で 1 ≤ k ≤ 10^15 である。
x は避ける数字を含まない番号札で、その順番は 10^15 以下である。
x は 50 桁まであり得る
出力
命令ごとに1行ずつ、命令の順に出力する。
カンマなしの数字だけで書く
例 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 になります。
1つ目の例の L 50 は桁の値が 4, 0 なので 4 × 9 + 0 = 36 です。
罠その1: 0 を除いたなら、ふつうの b 進法ではありません。
桁の値が 1 ~ b の「0 のない b 進法」なので、余りが 0 のときは桁の値 b を使い、k から b を引いてから割ります。
上の桁から1つ借りることになります。
1 と 2 だけを使う2つ目の例で、ふつうの 2 進法に直すと I 2 が 2 ではなく 21 になり、借りずにそのまま割ると 12 になります。
罠その2: 余りは下の桁から出てきます。
逆にしないと、1つ目の例の I 9 が 10 ではなく 01 になります。
罠その3: 番号札は 1 からです。
0 を使う建物でも 0 は番号札ではないので、k をそのまま b 進法に直します。
k - 1 を直すと、1つ目の例の I 4 が 5 ではなく 3 になります。
罠その4: 番号札は 50 桁まであるので、数として読んではいけません。
JS の Number(x) は 16 桁あたりから下の桁を失い、2つ目の例の最後の行が違ってしまいます。
解答は8つの言語すべてで番号札を文字列として読み、1文字ずつ見ます。
罠その5: 順番は 10^15 まであるので、32 ビットの int に入れるとあふれます。
Java で int にためると、2つ目の例の最後の行が -1530494977 になります。
解答は C・C++ で long long、Java・C# で long を使います。
JS・TS の number とクレイコンの数は 2^53 まで正確なので 10^15 は大丈夫で、Python の整数には大きさの制限がありません。
商は Python k // b、JS・TS Math.floor(k / b)、Java・C#・C・C++ は整数どうしの k / b、クレイコンは 数学.商(k, b) です。
数字が何番目の桁の値かは、Python allowed.index(ch)、JS・TS・Java indexOf、C# IndexOf、C strchr、C++ find、クレイコン 位置検索 で探します。

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]) + "");    }}