AtCoder Regular Contest 088 E - Papple Sort
今年の解き納め問題。
この回は都合で出られなかったけど、出てたらこれは解けていた気がする…
問題概要
文字列Sが与えられる。これを「隣り合った2つの文字を入れ替える」という操作を好きなだけ行い、回文に出来るか。可能なら、捜査の最小回数を求めよ。
解説
そもそも回文に出来るかですが、
回文を作れる⇔奇数個ある文字の種類が1個未満
なので、普通にカウントしてあげてください。
最小回数ですが、AtCoder解説と異なる解法です。
外側から順に文字を確定していく方式。考え方にコツがあって、
- 各文字の種類ごとに見た時、同じ種類同士の文字を入れ替える必要性は全く無いので、一番外側の文字のみを考えればよい。
という点があります。で、一番外側に動かすのに最も操作回数の少ない文字を求めて、それを貪欲に外側に持っていけばいいです。
各文字の種類でdequeにその文字がある座標を埋め込むとこれが楽に実装・削除できます(通常の配列では工夫しないと、1操作でO(N)かかるので注意)。
しかし、「現在どこにその文字があるか」が分からないのでそれを毎回探す必要があり、これに1回O(|S|)かかります。そこで、バブルソートの反転数でお馴染みのBitIndexedTreeを利用します。
Bit Indexed Treeでは「i以下の要素で、利用された要素数」を見つけることが出来れば、「利用した文字列は消し去るor一番右へスライドし、後は利用しない」という考え方で、dequeに格納された最初の状態から現在あるべき座標が求められます。
移動後の座標がx - sum(x)で求まるので、一番外側に何を持ってくればいいのか、それに何回かかるかがO(log|S|)で分かります。あとは実際に文字列を操作せず、dequeのみで管理する点に注意して、文字がなくなるまで繰り返せば最小手数が求まります。
実際はBITでなくても、セグ木でも平方分割でも通りそうですが、これが一番実装楽だと思います。
Submission #1929672 - AtCoder Regular Contest 088
deque<int> pos[26]; int bit[200010]; int N = 1; void init(int n) { N = n; REP(i, N) bit[i] = 0; } void add(int k, int a) { while (k <= N) { bit[k] += a; k += k&-k; } } int sum(int k) { int ret = 0; while (k>0) { ret += bit[k]; k &= k - 1; } return ret; } int main() { string s; cin >> s; int n = s.size(); rep(i, n) pos[s[i] - 'a'].push_back(i + 1); int cntodd = 0; rep(i, 26) cntodd += pos[i].size() & 1; if (cntodd > 1) { cout << -1 << endl; return 0; } ll ret = 0; init(n); while (n > 2) { int del = -1; int mov = inf; rep(i, 26) { if (pos[i].size() > 1) { int l = pos[i].front(), r = pos[i].back(); //適当に正しい位置へもっていき、 l -= sum(l); r -= sum(r); //両端への移動距離を計算する if (mov > (l - 1) + (n - r)) { mov = l + n - r - 1; del = i; } } } ret += mov; add(pos[del].front(), 1); add(pos[del].back(), 1); pos[del].pop_front(); pos[del].pop_back(); n -= 2; } cout << ret << endl; return 0; }
感想
ライブラリ持ってないんですよね…BITでバグらせてWA連発したのは痛かった。