{ }codelper
動的計画法64ビット整数P0279

棒が S 本つく番号

番号の電光掲示板は、数字一つを棒 7 本で表す。
数字ごとにつく棒の数は次のとおり:0 は 6 本、1 は 2 本、2 は 5 本、3 は 5 本、4 は 4 本、5 は 5 本、6 は 6 本、7 は 3 本、8 は 7 本、9 は 6 本。
掲示板に整数 x を表示すると、x を十進法で先頭に 0 を付けずに書いた数字がつく。
0 は数字 0 一つで表示する。
x の棒の数は、ついた数字の棒の数をすべて足した値である。
たとえば 108 の棒の数は 2 + 6 + 7 = 15 で、7 の棒の数は 3 である。
質問ごとに、L 以上 R 以下の整数のうち棒の数がちょうど S であるものがいくつあるかを出力せよ。
そのような整数がなければ 0 を出力する。

入力
一行目に質問数 Q、続く Q 行に質問ごとに「L R S」が空白一つで区切られて与えられる。
1 ≤ Q ≤ 2,000、0 ≤ L ≤ R ≤ 10^15(1,000,000,000,000,000)、1 ≤ S ≤ 200。
すべて整数
出力
Q 行:質問ごとに個数一つ(カンマなしの数字だけ)
例 1 · stdin
3
1 20 5
0 9 6
100 200 15
stdout
4
3
6
main.py
入力(stdin)

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

$ 準備完了

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

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

R は 10^15 まであるので、一つずつ数えることはできません。
桁 DP で解きます:ways[k][s] = 数字 k 個を自由に並べたとき棒の数の合計が s になる場合の数(k = 16、s ≤ 112 で足ります)。
「n 以下で棒の数が S の数」count(n) を作れば、答えは count(R) − count(L − 1) です。
count(n) は三つの部分です:① 0 自身(棒 6 本)② n より桁数が少ない数 — 先頭の数字は 1~9、残り k − 1 桁は ways で ③ n と桁数が同じ数 — 前から n と同じ数字をたどり、i 番目の桁で初めて小さい数字 d を選べば、後ろは ways[残りの桁][残りの棒] です(先頭の桁は 1 から)。
最後までたどった n 自身も一つ数えます。
例:1~20 で棒 5 本は 2、3、5、17(2 + 3)なので 4 個です。
落とし穴一:桁和を数えるよくある解き方のように、短い数の前に 0 を付けて同じ長さで数えると間違えます — 桁和では 0 は 0 なので問題ありませんでしたが、ここでは先頭の 0 ごとに棒 6 本がついたことになります。
桁数ごとに別々に数えるか、「もう数字を書き始めたか」を状態に入れます。
落とし穴二:0 自身を忘れると、L = 0、S = 6 の質問が一つ足りなくなります。
落とし穴三:count(R) − count(L) で引くと L 自身が抜けます。
落とし穴四:掲示板によっては 7 を棒 4 本、6・9 を 5 本で描くこともあります — この問題の表(7 は 3 本、6・9 は 6 本)のとおりに使います。
落とし穴五:答えは 10^15 近くになり、32 ビット int(約 21 億)を超えます。
C・C++ は long long、Java・C# は long が必要で(Java では int に += で long を足してもコンパイルが通り、黙って切り捨てられます — 複合代入が自動で型変換するからです。
C# はコンパイルエラーになります)、入力も Long.parseLong・long.Parse・%lld で読みます。
Python の整数は上限がなく、JavaScript・TypeScript・Craykon の数は倍精度浮動小数点数で 2^53(約 9×10^15)まで正確なので、そのままで大丈夫です。
数を数字の列にする方法:Python str(n)、JS・TS String(n)、Java Long.toString(n)、C# n.ToString()、C snprintf(buf, size, "%lld", n)、C++ std::to_string(n)、Craykon "" + n(10^15 も指数なしで書かれます)と digits[i].数に()。

Pythonmain.py · 53 行
import sys SEG = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]  # 숫자 0~9를 띄울 때 켜지는 막대 수MAXLEN = 16                          # 10^15는 16자리MAXS = 7 * MAXLEN # ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수ways = [[0] * (MAXS + 1) for _ in range(MAXLEN + 1)]ways[0][0] = 1for k in range(1, MAXLEN + 1):    for s in range(MAXS + 1):        if ways[k - 1][s]:            for d in range(10):                ways[k][s + SEG[d]] += ways[k - 1][s]  def tail(k, s):    """뒤에 숫자 k개가 남았고 막대를 s개 더 켜야 할 때의 경우의 수"""    return ways[k][s] if 0 <= s <= MAXS else 0  def count_upto(n, S):    """0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수"""    if n < 0:        return 0    total = 1 if S == SEG[0] else 0  # 0은 숫자 0 하나로 띄운다    if n == 0:        return total    digits = str(n)    length = len(digits)    # n보다 자릿수가 적은 수: 첫 숫자는 1~9(앞에 0을 붙이지 않는다), 나머지는 아무 숫자    for k in range(1, length):        for d in range(1, 10):            total += tail(k - 1, S - SEG[d])    # n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다    rest = S    for i, ch in enumerate(digits):        c = int(ch)        for d in range(1 if i == 0 else 0, c):            total += tail(length - 1 - i, rest - SEG[d])        rest -= SEG[c]    if rest == 0:        total += 1  # n 자신    return total  data = sys.stdin.read().split()q = int(data[0])out = []for i in range(q):    lo, hi, s = int(data[1 + 3 * i]), int(data[2 + 3 * i]), int(data[3 + 3 * i])    out.append(str(count_upto(hi, s) - count_upto(lo - 1, s)))print("\n".join(out))
JavaScriptmain.js · 50 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/); const SEG = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]; // 숫자 0~9를 띄울 때 켜지는 막대 수const MAXLEN = 16; // 10^15는 16자리const MAXS = 7 * MAXLEN; // ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수// 가장 큰 값도 10^15쯤이라 number(2^53까지 정확)로 충분하다const ways = Array.from({ length: MAXLEN + 1 }, () => new Array(MAXS + 1).fill(0));ways[0][0] = 1;for (let k = 1; k <= MAXLEN; k++) {  for (let s = 0; s <= MAXS; s++) {    if (ways[k - 1][s] === 0) continue;    for (let d = 0; d < 10; d++) ways[k][s + SEG[d]] += ways[k - 1][s];  }} const tail = (k, s) => (s >= 0 && s <= MAXS ? ways[k][s] : 0); // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수function countUpto(n, S) {  if (n < 0) return 0;  let total = S === SEG[0] ? 1 : 0; // 0은 숫자 0 하나로 띄운다  if (n === 0) return total;  const digits = String(n);  const len = digits.length;  // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자  for (let k = 1; k < len; k++) {    for (let d = 1; d <= 9; d++) total += tail(k - 1, S - SEG[d]);  }  // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다  let rest = S;  for (let i = 0; i < len; i++) {    const c = digits.charCodeAt(i) - 48;    for (let d = i === 0 ? 1 : 0; d < c; d++) total += tail(len - 1 - i, rest - SEG[d]);    rest -= SEG[c];  }  if (rest === 0) total += 1; // n 자신  return total;} const q = Number(data[0]);const out = [];for (let i = 0; i < q; i++) {  const lo = Number(data[1 + 3 * i]);  const hi = Number(data[2 + 3 * i]);  const s = Number(data[3 + 3 * i]);  out.push(countUpto(hi, s) - countUpto(lo - 1, s));}console.log(out.join("\n"));
TypeScriptmain.ts · 54 行
import { readFileSync } from "node:fs"; const data: string[] = readFileSync(0, "utf8").trim().split(/\s+/); const SEG: readonly number[] = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]; // 숫자 0~9를 띄울 때 켜지는 막대 수const MAXLEN = 16; // 10^15는 16자리const MAXS = 7 * MAXLEN; // ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수// 가장 큰 값도 10^15쯤이라 number(2^53까지 정확)로 충분하다const ways: number[][] = Array.from({ length: MAXLEN + 1 }, () => new Array<number>(MAXS + 1).fill(0));ways[0][0] = 1;for (let k = 1; k <= MAXLEN; k++) {  for (let s = 0; s <= MAXS; s++) {    if (ways[k - 1][s] === 0) continue;    for (let d = 0; d < 10; d++) ways[k][s + SEG[d]] += ways[k - 1][s];  }} function tail(k: number, s: number): number {  return s >= 0 && s <= MAXS ? ways[k][s] : 0;} // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수function countUpto(n: number, S: number): number {  if (n < 0) return 0;  let total = S === SEG[0] ? 1 : 0; // 0은 숫자 0 하나로 띄운다  if (n === 0) return total;  const digits = String(n);  const len = digits.length;  // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자  for (let k = 1; k < len; k++) {    for (let d = 1; d <= 9; d++) total += tail(k - 1, S - SEG[d]);  }  // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다  let rest = S;  for (let i = 0; i < len; i++) {    const c = digits.charCodeAt(i) - 48;    for (let d = i === 0 ? 1 : 0; d < c; d++) total += tail(len - 1 - i, rest - SEG[d]);    rest -= SEG[c];  }  if (rest === 0) total += 1; // n 자신  return total;} const q = Number(data[0]);const out: number[] = [];for (let i = 0; i < q; i++) {  const lo = Number(data[1 + 3 * i]);  const hi = Number(data[2 + 3 * i]);  const s = Number(data[3 + 3 * i]);  out.push(countUpto(hi, s) - countUpto(lo - 1, s));}console.log(out.join("\n"));
JavaMain.java · 55 行
import java.io.*;import java.util.*; public class Main {    static final int[] SEG = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6}; // 숫자 0~9를 띄울 때 켜지는 막대 수    static final int MAXLEN = 16; // 10^15는 16자리    static final int MAXS = 7 * MAXLEN;    // ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수    static final long[][] ways = new long[MAXLEN + 1][MAXS + 1];     static long tail(int k, int s) {        return s >= 0 && s <= MAXS ? ways[k][s] : 0;    }     // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수(답이 10^15까지 가서 long)    static long countUpto(long n, int S) {        if (n < 0) return 0;        long total = S == SEG[0] ? 1 : 0; // 0은 숫자 0 하나로 띄운다        if (n == 0) return total;        String digits = Long.toString(n);        int len = digits.length();        // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자        for (int k = 1; k < len; k++)            for (int d = 1; d <= 9; d++) total += tail(k - 1, S - SEG[d]);        // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다        int rest = S;        for (int i = 0; i < len; i++) {            int c = digits.charAt(i) - '0';            for (int d = i == 0 ? 1 : 0; d < c; d++) total += tail(len - 1 - i, rest - SEG[d]);            rest -= SEG[c];        }        if (rest == 0) total++; // n 자신        return total;    }     public static void main(String[] args) throws IOException {        ways[0][0] = 1;        for (int k = 1; k <= MAXLEN; k++)            for (int s = 0; s <= MAXS; s++)                if (ways[k - 1][s] != 0)                    for (int d = 0; d < 10; d++) ways[k][s + SEG[d]] += ways[k - 1][s];         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(br.readLine().trim());        StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++) {            StringTokenizer st = new StringTokenizer(br.readLine());            long lo = Long.parseLong(st.nextToken());            long hi = Long.parseLong(st.nextToken());            int s = Integer.parseInt(st.nextToken());            sb.append(countUpto(hi, s) - countUpto(lo - 1, s)).append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 57 行
using System;using System.Text; class Program{    static readonly int[] Seg = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 }; // 숫자 0~9를 띄울 때 켜지는 막대 수    const int MaxLen = 16; // 10^15는 16자리    const int MaxS = 7 * MaxLen;    // ways[k, s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수    static readonly long[,] Ways = new long[MaxLen + 1, MaxS + 1];     static long Tail(int k, int s) => s >= 0 && s <= MaxS ? Ways[k, s] : 0;     // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수(답이 10^15까지 가서 long)    static long CountUpto(long n, int S)    {        if (n < 0) return 0;        long total = S == Seg[0] ? 1 : 0; // 0은 숫자 0 하나로 띄운다        if (n == 0) return total;        string digits = n.ToString();        int len = digits.Length;        // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자        for (int k = 1; k < len; k++)            for (int d = 1; d <= 9; d++) total += Tail(k - 1, S - Seg[d]);        // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다        int rest = S;        for (int i = 0; i < len; i++)        {            int c = digits[i] - '0';            for (int d = i == 0 ? 1 : 0; d < c; d++) total += Tail(len - 1 - i, rest - Seg[d]);            rest -= Seg[c];        }        if (rest == 0) total++; // n 자신        return total;    }     static void Main()    {        Ways[0, 0] = 1;        for (int k = 1; k <= MaxLen; k++)            for (int s = 0; s <= MaxS; s++)                if (Ways[k - 1, s] != 0)                    for (int d = 0; d < 10; d++) Ways[k, s + Seg[d]] += Ways[k - 1, s];         int q = int.Parse(Console.ReadLine()!.Trim());        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            var p = Console.ReadLine()!.Split(' ', StringSplitOptions.RemoveEmptyEntries);            long lo = long.Parse(p[0]);            long hi = long.Parse(p[1]);            int s = int.Parse(p[2]);            sb.Append(CountUpto(hi, s) - CountUpto(lo - 1, s)).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 52 行
#include <array>#include <iostream>#include <string> constexpr int MAXLEN = 16; // 10^15는 16자리constexpr int MAXS = 7 * MAXLEN;constexpr std::array<int, 10> SEG = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6}; // 숫자 0~9를 띄울 때 켜지는 막대 수 // ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수std::array<std::array<long long, MAXS + 1>, MAXLEN + 1> ways{}; long long tail(int k, int s) { return s >= 0 && s <= MAXS ? ways[k][s] : 0; } // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수(답이 10^15까지 가서 long long)long long countUpto(long long n, int S) {    if (n < 0) return 0;    long long total = S == SEG[0] ? 1 : 0; // 0은 숫자 0 하나로 띄운다    if (n == 0) return total;    const std::string digits = std::to_string(n);    const int len = static_cast<int>(digits.size());    // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자    for (int k = 1; k < len; k++)        for (int d = 1; d <= 9; d++) total += tail(k - 1, S - SEG[d]);    // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다    int rest = S;    for (int i = 0; i < len; i++) {        int c = digits[i] - '0';        for (int d = i == 0 ? 1 : 0; d < c; d++) total += tail(len - 1 - i, rest - SEG[d]);        rest -= SEG[c];    }    if (rest == 0) total++; // n 자신    return total;} int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    ways[0][0] = 1;    for (int k = 1; k <= MAXLEN; k++)        for (int s = 0; s <= MAXS; s++)            if (ways[k - 1][s])                for (int d = 0; d < 10; d++) ways[k][s + SEG[d]] += ways[k - 1][s];     int q;    std::cin >> q;    for (int i = 0; i < q; i++) {        long long lo, hi;        int s;        std::cin >> lo >> hi >> s;        std::cout << countUpto(hi, s) - countUpto(lo - 1, s) << '\n';    }}
Cmain.c · 49 行
#include <stdio.h> #define MAXLEN 16 /* 10^15는 16자리 */#define MAXS (7 * MAXLEN) static const int SEG[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6}; /* 숫자 0~9를 띄울 때 켜지는 막대 수 *//* ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수 */static long long ways[MAXLEN + 1][MAXS + 1]; static long long tail(int k, int s) { return s >= 0 && s <= MAXS ? ways[k][s] : 0; } /* 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수(답이 10^15까지 가서 long long) */static long long count_upto(long long n, int S) {    if (n < 0) return 0;    long long total = S == SEG[0] ? 1 : 0; /* 0은 숫자 0 하나로 띄운다 */    if (n == 0) return total;    char digits[24];    int len = snprintf(digits, sizeof digits, "%lld", n);    /* n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자 */    for (int k = 1; k < len; k++)        for (int d = 1; d <= 9; d++) total += tail(k - 1, S - SEG[d]);    /* n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다 */    int rest = S;    for (int i = 0; i < len; i++) {        int c = digits[i] - '0';        for (int d = i == 0 ? 1 : 0; d < c; d++) total += tail(len - 1 - i, rest - SEG[d]);        rest -= SEG[c];    }    if (rest == 0) total++; /* n 자신 */    return total;} int main(void) {    ways[0][0] = 1;    for (int k = 1; k <= MAXLEN; k++)        for (int s = 0; s <= MAXS; s++)            if (ways[k - 1][s])                for (int d = 0; d < 10; d++) ways[k][s + SEG[d]] += ways[k - 1][s];     int q;    if (scanf("%d", &q) != 1) return 0;    for (int i = 0; i < q; i++) {        long long lo, hi;        int s;        scanf("%lld %lld %d", &lo, &hi, &s);        printf("%lld\n", count_upto(hi, s) - count_upto(lo - 1, s));    }    return 0;}
Craykonmain.cray · 90 行
SEG = [6, 2, 5, 5, 4, 5, 6, 3, 7, 6]; // 숫자 0~9를 띄울 때 켜지는 막대 수MAXLEN = 16; // 10^15는 16자리MAXS = 7 * MAXLEN; // ways[k][s] = 숫자 k개를 아무렇게나 늘어놓았을 때(0으로 시작해도 된다) 막대 수 합이 s인 경우의 수ways = [];繰り返す (k = 0; k <= MAXLEN; k++){    ways.追加(新リスト(MAXS + 1, 0));}ways[0][0] = 1;繰り返す (k = 1; k <= MAXLEN; k++){    繰り返す (s = 0; s <= MAXS; s++)    {        もし (ways[k - 1][s] != 0)        {            繰り返す (d = 0; d < 10; d++)            {                ways[k][s + SEG[d]] += ways[k - 1][s];            }        }    }} // 뒤에 숫자 k개가 남았고 막대를 s개 더 켜야 할 때의 경우의 수뒤(k, s){    もし (s < 0 || s > MAXS)    {        返す 0;    }    返す ways[k][s];} // 0 이상 n 이하의 정수 가운데 막대 수가 S인 것의 수세기(n, S){    もし (n < 0)    {        返す 0;    }    total = 0;    もし (S == SEG[0])    {        total = 1;    }    もし (n == 0)    {        返す total;    }    digits = "" + n;    len = digits.長さ;    // n보다 자릿수가 적은 수: 첫 숫자는 1~9, 나머지는 아무 숫자    繰り返す (k = 1; k < len; k++)    {        繰り返す (d = 1; d <= 9; d++)        {            total += 뒤(k - 1, S - SEG[d]);        }    }    // n과 자릿수가 같은 수: 앞에서부터 n과 같다가 처음 더 작아지는 자리를 고른다    rest = S;    繰り返す (i = 0; i < len; i++)    {        c = digits[i].数に();        시작 = 0;        もし (i == 0)        {            시작 = 1;        }        繰り返す (d = 시작; d < c; d++)        {            total += 뒤(len - 1 - i, rest - SEG[d]);        }        rest -= SEG[c];    }    もし (rest == 0)    {        total += 1;    }    返す total;} q = コンソール.行読み().空白除去().数に();繰り返す (i = 0; i < q; i++){    p = コンソール.数読み();    コンソール.書く(세기(p[1], p[2]) - 세기(p[0] - 1, p[2]));}