{ }codelper
文字列の組み立てP0095

数字の帯の切れ

一行目に整数 n と w、二行目に質問数 q、三行目に切れの番号 q 個が与えられる。
1 から n までの数を順に空白なしでつなげて長い数字の帯を作る(n = 12 なら "123456789101112")。
この帯を先頭から w 文字ずつ切って切れを作り、先頭から 1 番、2 番、… の切れと呼ぶ。
最後の切れは w 文字より短くてもよい。
質問された番号の切れを質問の順に一行に一つずつ出力せよ。

入力
1 ≤ n ≤ 1,000,000、1 ≤ w ≤ 100、1 ≤ q ≤ 100、切れの番号は 1 以上切れの数以下(同じ番号を何度聞いてもよい)
出力
質問ごとにその切れを一行
例 1 · stdin
12 5
3
3 1 2
stdout
01112
12345
67891
例 2 · stdin
10 4
1
3
stdout
910
main.py
入力(stdin)

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

$ 準備完了

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

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

n が 100 万なら帯は約 590 万文字なので、つなぎ方で速さが大きく変わります。
Java String・C# string は変更できない値なので、s += x のたびにそれまでの文字をすべてコピーした新しい文字列を作ります。
ループの中で使うと O(n²) で時間切れになるので、StringBuilder の append/Append で集めます。
Python の文字列も変更できない値です。
CPython には += を速くする最適化がありますが、言語が約束する動作ではないので、切れを集めて "".join(...) で一度につなぐのが定石です。
JS・TS のエンジン(V8)は += を内部で切れをつないでおく方式で扱うので速いです。
配列に集めて join("") してもかまいません。
C++ std::string の += は後ろへの追加なので速いですが、s = s + x は毎回コピーを作ります。
C には文字列型がありません。
strcat は追加のたびに文字列の終わりを先頭から探すので、これも O(n²) です。
sprintf が返す文字数で終わりの位置を覚えておき、そこに直接書きます。
切れの番号は 1 から数えるので、k 番の切れは (k - 1) * w 番目の文字(0 から)から始まります。
Craykon は ribbon += i で数を文字列の後ろにつなぎ(JS の上で動くので速い)、ribbon.切り出す(開始, 個数) で切れを切り出します。

Pythonmain.py · 8 行
n, w = map(int, input().split())q = int(input())ks = list(map(int, input().split()))# 조각을 목록에 모았다가 join 한 번으로 잇는다ribbon = "".join(str(i) for i in range(1, n + 1))for k in ks:    start = (k - 1) * w    print(ribbon[start:start + w])
JavaScriptmain.js · 7 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, w] = lines[0].trim().split(/\s+/).map(Number);const ks = lines[2].trim().split(/\s+/).map(Number);const parts = [];for (let i = 1; i <= n; i++) parts.push(i);const ribbon = parts.join("");console.log(ks.map((k) => ribbon.slice((k - 1) * w, k * w)).join("\n"));
TypeScriptmain.ts · 8 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, w]: number[] = lines[0].trim().split(/\s+/).map(Number);const ks: number[] = lines[2].trim().split(/\s+/).map(Number);const parts: number[] = [];for (let i = 1; i <= n; i++) parts.push(i);const ribbon: string = parts.join("");console.log(ks.map((k) => ribbon.slice((k - 1) * w, k * w)).join("\n"));
JavaMain.java · 19 行
import java.util.Scanner; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int n = sc.nextInt();        int w = sc.nextInt();        int q = sc.nextInt();        // String += 는 매번 새 문자열을 만든다 — StringBuilder 하나에 이어 붙인다        StringBuilder ribbon = new StringBuilder();        for (int i = 1; i <= n; i++) ribbon.append(i);        StringBuilder out = new StringBuilder();        for (int j = 0; j < q; j++) {            int start = (sc.nextInt() - 1) * w;            out.append(ribbon, start, Math.min(start + w, ribbon.length())).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 21 行
using System;using System.Text; class Program{    static void Main()    {        string[] t = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(t[0]), w = int.Parse(t[1]);        Console.ReadLine();        // string += 는 매번 새 문자열을 만든다 — StringBuilder 하나에 이어 붙인다        var ribbon = new StringBuilder();        for (int i = 1; i <= n; i++) ribbon.Append(i);        string s = ribbon.ToString();        foreach (string k in Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries))        {            int start = (int.Parse(k) - 1) * w;            Console.WriteLine(s.Substring(start, Math.Min(w, s.Length - start)));        }    }}
C++main.cpp · 14 行
#include <iostream>#include <string> int main() {    int n, w, q, k;    std::cin >> n >> w >> q;    // std::string의 += 는 뒤에 덧붙인다(용량을 넉넉히 늘려 가서 빠르다)    std::string ribbon;    for (int i = 1; i <= n; i++) ribbon += std::to_string(i);    for (int j = 0; j < q; j++) {        std::cin >> k;        std::cout << ribbon.substr(static_cast<std::size_t>(k - 1) * w, w) << '\n';    }}
Cmain.c · 18 行
#include <stdio.h> static char ribbon[6000000]; int main(void) {    int n, w, q, k;    scanf("%d %d %d", &n, &w, &q);    // 끝 자리(len)를 기억하며 그 뒤에 바로 쓴다 — strcat은 매번 처음부터 끝을 찾는다    int len = 0;    for (int i = 1; i <= n; i++) len += sprintf(ribbon + len, "%d", i);    for (int j = 0; j < q; j++) {        scanf("%d", &k);        int start = (k - 1) * w;        int count = len - start < w ? len - start : w;        printf("%.*s\n", count, ribbon + start);    }    return 0;}
Craykonmain.cray · 15 行
t = コンソール.数読み();n = t[0];w = t[1];q = コンソール.行読み().数に();ks = コンソール.数読み();ribbon = "";繰り返す (i = 1; i <= n; i++){    ribbon += i;}それぞれ (k の中 ks){    start = (k - 1) * w;    コンソール.書く(ribbon.切り出す(start, 数学.小さい方(w, ribbon.長さ - start)));}