隣接 swap 操作を行いながら最小回数でソートする系の問題は、過去に何度も出てる!!!
これらはいずれも自然に DP で解くことができる。今回も。なお、「どれを表にするかを決め打って転倒数を求める」という方法もあるっぽい。
問題概要
表に 、裏に と書かれた 枚のカードが一列に並んでいる。以下の操作を繰り返すことで、表に出ているカードが広義単調増加となるようにしたい。最小回数を求めよ。
- 隣り合う 2 枚のカードを選んで swap し、ともに裏返す
制約
考えたこと
操作がわかりづらいけど、もともと i 番目にあったカードが最終的に j 番目に移動するとき
- | i - j | が偶数ならば、表の数値は A[ i ]
- | i - j | が奇数ならば、表の数値は B[ i ]
になるというイメージ。これを踏まえて、最終的な位置がどうなるべきなのかを求めていくことになる。
そして、この手の隣接 swap 系の問題は、似た雰囲気の DP でできることが多い。今回は
- dp[ bit ][ s ] := 枚のカードのうち、bit で表されるものについては左詰めしてソートされている状態で、最後の値が である状態にするまでに必要な操作回数
注意点として、bit の中身が 枚であるとき、bit で表されないカードについては、 番目以降に配置され直されることになる。なので、dp[ bit ][ s ] の状態で、bit に含まれないカードがそれぞれ何番目に対応するのかを対応付ける必要がある。
dp[ bit ][ s ] に新たに i 番目のカードを加えるとき、そのカードの現在地が p 番目であるとして、
- chmin( dp[ bit | (1 << i) ][ t ], dp[ bit ][ s ] + abs(c - p) )
という感じになる。t の部分は、
- | i - c | が偶数のときは A[ i ]
- | i - c | が奇数のときは B[ i ]
ということになる。t の値が s よりも小さい場合は遷移しない。ここでは は絶対値を用いているけど、実際は 種類の値に用いることができる。その場合、計算量は になる。
#include <iostream> #include <vector> #include <cmath> using namespace std; template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; } template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } using pint = pair<int,int>; const int INF = 1<<29; int N; vector<int> A, B; int solve() { vector<vector<int>> dp((1<<N)+1, vector<int>(55, INF)); dp[0][0] = 0; for (int bit = 0; bit < (1<<N); ++bit) { int con = 0; for (int i = 0; i < N; ++i) if (bit & (1<<i)) ++con; // bit に含まれない残りの要素が何番目に対応するか vector<pint> ords; int iter = con; for (int i = 0; i < N; ++i) if (!(bit & (1<<i))) ords.emplace_back(i, iter++); for (int s = 0; s <= 50; ++s) { if (dp[bit][s] >= INF) continue; for (auto p : ords) { // p.first: もともとの a の index // p.second: dp[bit][s] の状態での index int ns = -1; if (abs(p.first - con) % 2 == 0) ns = A[p.first]; else ns = B[p.first]; if (ns >= s) { chmin(dp[bit|(1<<p.first)][ns], dp[bit][s] + abs(p.second - con)); } } } } int res = INF; for (int s = 0; s <= 50; ++s) chmin(res, dp[(1<<N)-1][s]); if (res < INF) return res; else return -1; } int main() { cin >> N; A.resize(N), B.resize(N); for (int i = 0; i < N; ++i) cin >> A[i]; for (int i = 0; i < N; ++i) cin >> B[i]; cout << solve() << endl; }