実験していくうちにシンプルな構造がわかった。
問題概要
英文字からなる長さ の文字列 が与えられる。この文字列に対して、次の操作を 回行ってできる文字列を考える。
【操作】
文字列 に対して、英小文字と英大文字を入れ替えてできる文字列を として、 を で置き換える
この文字列に対して、 回のクエリ「整数 が与えられるので、先頭から 番目の文字に答えよ」に答えよ。
制約
考えたこと
0-indexed で考えることにする。クエリとして与えられる の値もデクリメントしておくこととする。
さて、もとの文字列 に対して、大文字と小文字を入れ替えた文字列を としよう。このとき、操作を十分回数行うと、
をこの順に連結したものとなる。まず一つ重要なことは、 を で割った商を 、余りを とすると、求める文字は、
- 上記の文字列の列の 番目の文字列について
- 先頭から 番目の文字
であるということだ。よって、問題は次のものに帰着される。
文字列の列
について、 番目のものは のいずれか?
文字列の列を考える
文字列の列に何か規則性がないかと考えてみよう。注目したいことは、「文字列の列」の作り方からして、文字列の列の 番目は、 と交互になっていることだ(文字列の列の作り方から明らか)。
このことから、きっと文字列の列の 番目の値は、 を二進法で表すと、良いことがあるのではないかと考えた。やってみよう!!
番目 | 番目(二進法) | 文字列 | 二進法の 1 の個数 |
---|---|---|---|
0 | 0 | 0 | |
1 | 1 | 1 | |
2 | 10 | 1 | |
3 | 11 | 2 | |
4 | 100 | 1 | |
5 | 101 | 2 | |
6 | 110 | 2 | |
7 | 111 | 3 | |
8 | 1000 | 1 | |
9 | 1001 | 2 | |
10 | 1010 | 2 | |
11 | 1011 | 3 | |
12 | 1100 | 2 | |
13 | 1101 | 3 | |
14 | 1110 | 3 | |
15 | 1111 | 4 |
これを見ると、文字列の列の 番目の項は、 を二進法で表したときの 1 の個数を として、
- が偶数のとき:
- が奇数のとき:
となることがわかる。証明は、一般に 未満の整数 に対して、 であることから導ける(詳細は公式解説より)。
計算量は となる。
コード
#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; }