A - Reverse and Count 解説
by
Nyaan
まず、ナイーブなアルゴリズムとして、実際に \(N(N+1)/2\) 通りの数列をすべて列挙してソートする解法が考えられますが、計算量が \(\mathrm{O} (N^3 \log N)\) になりこれは TLE します。
計算量を改善します。答えを先頭の要素から決めていくことを考えます。
反転という操作の性質から、\(f(L,R)\) の先頭の要素は \(L, R\) によって次の 2 通りに場合分けされます。(ここで、数列 \(a\) の \(n\) 番目の要素を \(a[n]\) とします)
- \(L = 1\) かつ \(R \neq 1\) の場合 : \(f(L, R)[1] = A_R\)
- それ以外の場合:\(f(L, R)[1] = A_1\)
\(A_R\) の \(A_1\) との大小関係に注目してみます。\(A_1, A_2, A_3, \dots, A_N\) のうち \(A_1\) 未満の要素が \(x\) 個, \(A_1\) より大きい要素が \(y\) 個あるとします。すると、 適切な数列 \(s_1, \dots, s_x, t_1, \dots, t_y\) を用いて
\[ A_{s_1} \lt A_{s_2} \lt \dots \lt A_{s_x} \lt A_1 \lt A_{t_1} \lt A_{t_2} \lt \dots \lt A_{t_y} \]
と表せます。(\(A\) は順列なので全ての要素は相異なるため、各項は \(\lt\) で結ばれるのに注意してください) ここから、\(f(L, R)\) として有り得る列の \(1\) 番目の要素を比較すると次の式になります。
\[ \begin{aligned} &f(1, s_1)[1] \lt f(1, s_2)[1] \lt \dots \lt f(1, s_x)[1] \\ &\lt (それ以外の数列)[1] \\ &\lt f(1, t_1)[1] \lt f(1, t_2)[1] \lt \dots \lt f(1, t_y)[1] \end{aligned} \]
この式から、\(f(L, R)\) の列のうち小さい方から \(x\) 個と大きい方から \(y\) 個は \(s_1, \dots, s_x\) あるいは \(t_1, \dots, t_y\) を求めることで計算できます。よって、\(K\) がどちらか一方の範囲に入っている場合は該当する \(L, R\) の組を求められます。
求めたい \(f(L, R)\) の \(1\) 番目が \(A_1\) と一致する場合を考えます。\(1\) 番目の要素が \(A_1\) である \(f(L, R)\) のみを対象として考えると、 \(2\) 番目の要素に注目すれば、\(A_2\) と \(A_3, \dots, A_N\) の大小関係を考えた上で同様に上位の数個と下位の数個を特定できます。
このような操作を \(2\) 番目の要素, \(3\) 番目の要素, \(\dots\) \(N\) 番目の要素を対象として繰り返していくことで、求めたい \(K\) 番目の数列と \(A\) が一致しない場合の答えを検出できます。
\(K\) 番目の順列が \(A\) と一致している場合は、この方法で \(N\) 番目の要素まで調べても最後まで \(L, R\) の組を発見できません。この場合は答えは \(A\) と完全に一致するので \(A\) をそのまま出力します。
全体の計算量は \(\mathrm{O}(N^2)\) になり、十分高速です。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int N, K;
cin >> N >> K;
vector<int> A(N);
for (auto& x : A) cin >> x;
int a = 1, b = N * (N + 1) / 2;
for (int i = 0; i < N; i++) {
vector<int> small, large;
for (int j = i + 1; j < N; j++) {
if (A[j] < A[i]) small.push_back(A[j]);
if (A[j] > A[i]) large.push_back(A[j]);
}
int x = -1;
if (K - a < int(small.size())) {
sort(begin(small), end(small));
x = small[K - a];
}
if (b - K < int(large.size())) {
sort(begin(large), end(large));
reverse(begin(large), end(large));
x = large[b - K];
}
if (x != -1) {
int j = i;
while (A[j] != x) j++;
reverse(begin(A) + i, begin(A) + j + 1);
break;
}
a += small.size();
b -= large.size();
}
for (int i = 0; i < N; i++) cout << A[i] << " \n"[i + 1 == N];
}
投稿日時:
最終更新: