けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 380 D - Strange Mirroring (1Q, 緑色, 350 点)

実験していくうちにシンプルな構造がわかった。

問題概要

英文字からなる長さ  N の文字列  S が与えられる。この文字列に対して、次の操作を  10^{100} 回行ってできる文字列を考える。

【操作】
文字列  S に対して、英小文字と英大文字を入れ替えてできる文字列を  T として、 S を  S + T で置き換える

この文字列に対して、 Q 回のクエリ「整数  K が与えられるので、先頭から  K 番目の文字に答えよ」に答えよ。

制約

  •  1 \le N, Q \le 2 \times 10^{5}
  •  1 \le K \le 10^{18}

考えたこと

0-indexed で考えることにする。クエリとして与えられる  K の値もデクリメントしておくこととする。

さて、もとの文字列  S に対して、大文字と小文字を入れ替えた文字列を  T としよう。このとき、操作を十分回数行うと、

 S, T, T, S, T, S, S, T, T, S, S, T, S, T, T, S, \dots

をこの順に連結したものとなる。まず一つ重要なことは、 K を  N で割った商を  q、余りを  r とすると、求める文字は、

  • 上記の文字列の列の  q 番目の文字列について
  • 先頭から  r 番目の文字

であるということだ。よって、問題は次のものに帰着される。


文字列の列

 S, T, T, S, T, S, S, T, T, S, S, T, S, T, T, S, \dots

について、 q 番目のものは  S, T のいずれか?


文字列の列を考える

文字列の列に何か規則性がないかと考えてみよう。注目したいことは、「文字列の列」の作り方からして、文字列の列の  0, 1, 3, 7, 15, 31, \dots 番目は、 S, T, S, T, S, T, \dots と交互になっていることだ(文字列の列の作り方から明らか)。

このことから、きっと文字列の列の  n 番目の値は、 n を二進法で表すと、良いことがあるのではないかと考えた。やってみよう!!

番目 番目(二進法) 文字列 二進法の 1 の個数
0 0  S 0
1 1  T 1
2 10  T 1
3 11  S 2
4 100  T 1
5 101  S 2
6 110  S 2
7 111  T 3
8 1000  T 1
9 1001  S 2
10 1010  S 2
11 1011  T 3
12 1100  S 2
13 1101  T 3
14 1110  T 3
15 1111  S 4

これを見ると、文字列の列の  n 番目の項は、 n を二進法で表したときの 1 の個数を  f(n) として、


  •  f(n) が偶数のとき: S
  •  f(n) が奇数のとき: T

となることがわかる。証明は、一般に  2^{m} 未満の整数  n に対して、 f(n + 2^{m}) = f(n) + 1 であることから導ける(詳細は公式解説より)。

計算量は  O(N + Q \log K) となる。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    long long Q, K;
    string S;
    cin >> S >> Q;

    auto flip = [&](char c) -> char {
        if (islower(c)) return toupper(c);
        else return tolower(c);
    };

    while (Q--) {
        cin >> K;
        K--;
        long long q = K / S.size(), r = K % S.size();

        long long num = 0;
        for (int i = 0; i < 61; i++) if (q & (1LL << i)) num++;
        if (num % 2 == 0) cout << S[r] << " ";
        else cout << flip(S[r]) << " ";
    }
    cout << endl;
}