実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 849
問題概要
英文字からなる文字列 が与えられる。
に以下の操作を
回繰り返す。
- まず、
の大文字を小文字に、小文字を大文字に書き換えた文字列を
とする。
- その後、
と
とをこの順に連結した文字列を新たな
とする。
以下の 個のクエリを処理せよ。
番目のクエリ : 全ての操作を終えた後の
の先頭から
文字目を出力する。
制約
は整数。
考察
回の操作をすべてを実行するのは不可能だが、何らかの規則性はありそう。ということで、まずは実験してみる。
ただし、 という表し方をしている。これを見ると、
の長さは操作を行うごとに倍々になっていき、
の前半は
に、後半は
の大文字・小文字を反転した
に一致していることが分かる。
あるステップに、その前のステップのものが含まれているような性質を自己相似性といい、その代表的なものがフラクタル図形である。この性質を持つものは、再帰的な探索が有効なことが多い。
実際、本問の背景には Thue-Morse 数列 という自己相似性を持つ数列があるらしい。
では、今回も再帰関数の方針で解いてみる。
操作後の文字列からさかのぼっていくことを考えると、最終的に出力すべき文字のインデックス が
の前半と後半のどちらに属しているかで処理が変わってくる。
具体的には、再帰関数を とすると、以下のような実装となる。ただし、
、
を文字列
の大文字・小文字を反転する関数とした。
ならば、
を出力する (ベースケース) 。
- そうでないとき、
として、
(
が
の前半にある) ならば、
を呼び出す。
- そうでない (
が
の後半にある) ならば、
を呼び出す。
このもとで、 からさかのぼっていけばよい (
なので、60回操作後の
からで十分) 。
コード
#include <bits/stdc++.h> using namespace std; using ll = long long; // ======================================== // char flip(char c) { if (islower(c)) return toupper(c); return tolower(c); } char f(const string &S, ll K, ll current_len) { if (current_len == S.size()) { return S[K]; } ll prev_len = current_len / 2; if (K < prev_len) { return f(S, K, prev_len); } else { return flip(f(S, K - prev_len, prev_len)); } } int main() { string S; int Q; cin >> S >> Q; ll current_len = S.size(); while (current_len < 1e18) { current_len *= 2; } while (Q--) { ll K; cin >> K; K--; cout << f(S, K, current_len) << endl; } }
実装時間: 30分