mini notes

競技プログラミングの解法メモを残していきます。

キーエンス プログラミング コンテスト 2020 D - Swap and Flip (700)

D - Swap and Flip

概要

カードがN枚ある。i番目のカードの表面には1以上の整数A[i]が、裏面には1以上の整数B[i]がそれぞれ記載されている。
最初、全てのカードが表面を上にして置かれている。この状態から次の操作を好きなだけ繰り返し、見えているカードの数字が広義単調増加となるようにできるとき、操作回数の最小値を求めよ。

  • 隣同士となっているカードを選び、それらを交換したのち、それらのカードを裏返す
制約

1 ≦ N ≦ 18
1 ≦ A[i], B[i] ≦ 50

方針

仮に裏表の区別がない場合は、{1, 2, ..., N}のカードが{p(1), p(2), ... p(n)}の位置になった場合、{p(1), p(2), ... p(n)}の転倒数が答えとなる。
転倒数の解説は下記を参考にしました。
ikatakos.com

実は、各カードは最終的な位置により表裏が一意に定まる。具体的には最初の状態でi番目のカードがj番目になるとき、iとjの偶奇が同じ⇒表面 それ以外⇒裏面 となる。

なので、カードの順列を与える⇒裏表が一意に決まる⇒その数列が広義単調増加なら転倒数が答えの候補 と考えることができる。しかし、このままだと順列を与えることになり、計算が間に合わない。

bitDPが使える。dp[bit][j] を次のように定義する。
bit: n桁の2進数、桁iが1⇔i番目のカードがすでに使用される(使用されたカードは未使用のカードより左にあり、かつソートされている)
j :すでに使用されたカードの最大値
dp[bit][j] 上記bit, j の下で、それまでに使用されたカードの転倒数の最小値

dpは配るdpで実装する。
各bitのループの最初にまだ使用されていないカードについてもともとの位置とbit状態での位置(使用されているカードがあるため、最初の状態から位置が変わる)を記憶しておく。
使用されていない全てのカードについて、使用されたカードの最後尾に置くことができる(裏表考慮後のカードの数字がj以上であるならOK)場合は、dpを更新する。このときdpには現在のカードの転倒数が加算される。使用されたカードをcnt枚、そのカードの現在の位置をp.secondとすると、con番目以降のカードは初期状態のままであり、p.secondカードと順位が逆転することになるため、そのカードの枚数分(p.second - con)転倒数に加算される。

感想

けんちょんさんのブログを大いに参考にしました。
drken1215.hatenablog.com

解答

Submission #9948554 - Keyence Programming Contest 2020

#include <bits/stdc++.h>
#define rep(i,n) for(int i=(0);i<(n);i++)
 
using namespace std;
 
typedef long long ll;
typedef pair<int, int> pii;
 
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
 
	int n;
	cin >> n;
 
	int a[n], b[n];
	rep(i, n) cin >> a[i];
	rep(i, n) cin >> b[i];
 
	int max_bit = (1 << n) - 1;
	int max_a = 52;
	int dp[max_bit + 1][max_a + 1];
 
	int INF = 1010101;
	rep(i, max_bit + 1) rep(j, max_a+1) dp[i][j] = INF;
	dp[0][0] = 0;
 
	rep(bit, max_bit + 1){
		int cnt = __builtin_popcount(bit);
 
		int t = cnt;
		vector<pii> v;
 
		rep(i, n){
			if(!((bit >> i) & 1)){
				v.push_back({i, t++});
			}
		}
		
		rep(j, max_a + 1){
			if(dp[bit][j] == INF) continue;
 
			for(pii p : v){
				int ns;
				if(abs(p.first - cnt) % 2 == 0){
					ns = a[p.first];
				}else{
					ns = b[p.first];
				}
 
				if(ns >= j){
					dp[bit | (1 << p.first)][ns] 
				     = min(dp[bit | (1 << p.first)][ns], dp[bit][j] + (p.second - cnt));
				}
				
			}
			
		}
	}
 
	int ans = INF;
	rep(i, max_a+1) ans = min(ans, dp[max_bit][i]);
	if(ans == INF) ans = -1;
 
	cout << ans << endl;
}