SRM 545 div1 easy: StrIIRec
問題
TopCoder Statistics - Problem Statement
a, b, c, ... (a から n 番目の文字) を 1 回ずつ使って長さ n の文字列 s を作る。このとき, 以下の条件が成り立つようにしたい。
- s は minStr よりも辞書順で後ろ
- s は最低でも minInv 個の反転文字を持つ(i < j, s[i] > s[j] というやつ)
これらを満たす辞書順で最小の文字列を求めよ。
解法
貪欲に解けます。
まず, n 文字で得られる最大の反転文字数は n*(n-1)/2 となることに注意します。
例えば先頭の文字 c を決定した場合, 反転文字の個数の最大値は (c 未満のアルファベットの数) - (n-1 文字で得られる最大の反転文字数) となり, もしこれが minInv よりも小さかったら先頭の文字を c にしてはいけません。逆にこれが minInv より大きく, かつ「minStr より辞書順で小さくなることがない」ことが成り立つのであれば, 貪欲に c を選ぶべきです。
これを繰り返せば文字列を得られます。
int calc(int n) { return n*(n-1)/2; } bool done[22]; class StrIIRec { public: string recovstr(int n, int minInv, string minStr) { memset(done, false, sizeof(done)); string ret; bool flag = false; int now = 0; for (int i = 0; i < n; i++) { int index = -1; cout << i << " " << now << " " << ret << endl; for (int j = 0; j < n; j++) { if (done[j]) continue; char c = (char)('a'+j); int cnt = 0; for (int k = 0; k < j; k++) if (!done[k]) cnt++; cnt += calc(n-i-1); bool ok = (i >= minStr.size()); if (!ok) ok |= (flag || c >= minStr[i]); if (now+cnt >= minInv && ok) { index = j; now += cnt-calc(n-i-1); flag |= c > minStr[i]; break; } } if (index == -1) return ""; ret += (char)('a'+index); done[index] = true; } return ret; } };