本番 14 分かけたのは反省。
問題概要
"110" という文字列を 10000000000 個連結してできる文字列を とする。
長さ の文字列 が与えられる。 の中に が連続する部分文字列としていくつ含まれるかを求めよ。
制約
考えたこと
こういう、端の処理が慎重を要するタイプの問題、実はめっちゃ苦手なのです...
とりあえず とする。 は左右両端の端の部分を除くと "110" の繰り返しになっていなければならない。僕は両端から削る方針でやった。
まずは次の処理をする。
- T = "1" は例外的、答えは となる
- T = "0", "11" の場合は、答えは となる
- T の最初の 1 文字が "0" の場合は、それを除去して、 からも最初の "110" を除去する ( をデクリメントする)。
- T の最初の 2 文字が "10" の場合は、それを除去して、 からも最初の "110" を除去する ( をデクリメントする)。
そうすると、 は "110110...110" という形になるはず ( = "", "1", "11")。ならなければ答えは 0。そして末尾の * が空文字でないならば、それを除去して、 からも最後の "110" を除去する ( をデクリメントする)。
この段階で の "110" の繰り返し数を としたとき、答えは となる ( のとき 個になることを想像するとわかりやすい)。
計算量は 。
#include <bits/stdc++.h> using namespace std; int main() { int N; string T; cin >> N >> T; auto solve = [&]() -> long long { long long V = 10000000000LL; if (T == "0") return V; else if (T == "1") return V * 2; if (T.size() >= 1 && T.substr(0, 1) == "0") T = T.substr(1), --V; else if (T.size() >= 2 && T.substr(0, 2) == "10") T = T.substr(2), --V; if (T.size() % 3 == 1) { if (T.substr(T.size()-1) != "1") return 0; T = T.substr(0, T.size()-1); --V; } else if (T.size() % 3 == 2) { if (T.substr(T.size()-2) != "11") return 0; T = T.substr(0, T.size()-2); --V; } for (int i = 0; i < T.size(); i += 3) if (T.substr(i, 3) != "110") return 0; return V - T.size()/3 + 1; }; cout << solve() << endl; }
別解:開始位置を全探索
もう少し明快なロジックがありそう。 は周期 3 なので、 における の開始位置を 3 通り全探索してしまうという考え方もできる。if 文で書くより for 文を回して探索できるなら、そっちの方がいい。
の 0 文字目、1 文字目、2 文字目からマッチングさせていって、もしマッチするならば、 中の費やした "110" のブロック数を とすると、
result += V - a + 1;
というように、答えに加算していけばよい。
#include <bits/stdc++.h> using namespace std; int main() { int N; string S = "110", T; cin >> N >> T; auto solve = [&]() -> long long { long long V = 10000000000LL; if (T == "0") return V; else if (T == "1") return V * 2; long long res = 0; for (int i = 0; i < 3; ++i) { long long a = (T.size() + i + 2) / 3; long long add = V - a + 1; bool ok = true; for (int j = 0; j < T.size(); ++j) { if (T[j] != S[(j+i)%3]) ok = false; } if (ok) res += add; } return res; }; cout << solve() << endl; }