ICPCOOC2016 D: Hidden Anagrams
問題
http://judge.u-aizu.ac.jp/onlinejudge/contest/ICPCOOC2016/D.pdf
2つの文字列 がある。一つの部分文字列のアナグラムが、他方の部分文字列に一致するような文字列の最長の長さを求めよ。
解法
アナグラムを考えれば良いので、部分文字列について文字の順序は関係ないことになる。よって累積和を取り、区間についてで含まれている種類ごとの文字数を特定する方法が思いつく。一方の部分文字列を文字数カウントを、個あるそれぞれの区間についてサイズ26の配列として持ち、二分探索木につめる解法が考えられる。そうすることで、区間から平衡二分探索木につめるのに ( は文字の種類数) になると考えられる。
しかしこれではなかなか重い上にMLEする。また、ローリングハッシュの要領でサイズ26の配列を1つの64bit整数にまとめる方法も考えられる。空間計算量から文字の種類数 を消すことができるが、これでもまだMLEは避けられない。平衡二分探索木(set)をハッシュテーブル(unordered_set)にしても、実行速度とメモリの面で多少の改善しか見られない。
区間の全てを平衡二分探索木につめるのをやめて、長さごとに分割して詰める方針を取る。 の長さに対して、全ての始点をとった部分文字列を平衡二分探索木に詰めたら、その時点でアナグラムの一致判定をする。こうすることで計算量を増やさずにMLEを回避することができ、当日のオープンコンテストの環境でもACすることができる。
array<int, 26> sum[2][4010]; typedef unsigned long long ull; const ull B = 1^9+7; int main() { string S[2]; int N[2]; rep(i, 2) { cin >> S[i]; N[i] = S[i].size(); } rep(k, 2) rep(i, N[k]) { rep(j, 26) sum[k][i + 1][j] += sum[k][i][j]; sum[k][i + 1][S[k][i]-'a']++; } int ans = 0; REP(length, 1, min(N[1], N[0]) + 1) { unordered_set<ull> st; rep(i, N[1] - length + 1) { auto subsum = sum[1][i + length]; rep(k, 26) subsum[k] -= sum[1][i][k]; ull hash = 0; rep(k, 26) hash = hash * B + subsum[k]; st.insert(hash); } rep(i, N[0]) { auto subsum = sum[0][i + length]; rep(k, 26) subsum[k] -= sum[0][i][k]; ull hash = 0; rep(k, 26) hash = hash * B + subsum[k]; if(st.count(hash)) { ans = max(ans, length); } } } cout << ans << endl; return 0; }
感想
オープンコンテスト本番はMLEを回避する方法が思いつかず解くことができなかった。