TopCoder SRM 548 Div2 Hard KingdomAndPassword

問題

n桁の0を含まない数字からなるパスワードがある。
新しいパスワードを、このパスワードの桁を入れ替えて作る。


ただし、i番目に数字restrictedDigits[i]が来てはならない。
作れるパスワードが複数あるときは、|古いパスワード-新しいパスワード|が最小になるものを作るものとし、それも複数ある場合はその中で最小のものを作るものとする。


新しいパスワードを求めよ。
パスワードを作ることが出来ない場合-1を返せ。

制約条件

n≦16
restrictedDigits[]の要素はちょうどn個

方針

典型的な桁DP + 経路復元。
なのだが、|古いパスワード-新しいパスワード|を最小化するのが少し厄介。


自分は、i桁目までで、ビットの集合jを使い、
(古いパスワード-新しいパスワード)が正の場合の最小値と、
負の場合の最大値を覚えてDPした。


これは、先頭からDPしていって、符号が一度確定したら、後は符号は全部同じなので、
絶対値が最小のものだけを覚えればいいため。

ソースコード

int d[20], n, prev[17][1 << 16][2];
ll dp[17][1 << 16][2];

class KingdomAndPassword {
	public:
	long long newPassword(long long oldPassword, vector <int> restrictedDigits) 
	{
		n = 0;
		for(; oldPassword; oldPassword /= 10) d[n++] = oldPassword % 10;
		reverse(d, d + n);
		
		rep(i, n + 1) rep(j, 1 << n) rep(k, 2) dp[i][j][k] = inf;
		dp[0][0][0] = 0;
		
		rep(i, n) rep(j, 1 << n) rep(s, 2) if(dp[i][j][s] != inf) rep(k, n){
			if((j & 1 << k) || restrictedDigits[i] == d[k]) continue;
			
			ll nxt = dp[i][j][s] * 10 + d[i] - d[k];
			int ns = nxt < 0;
			if(abs(dp[i + 1][j | 1 << k][ns]) > abs(nxt)){
				dp[i + 1][j | 1 << k][ns] = nxt;
				prev[i + 1][j | 1 << k][ns] = k * 2 + s;
			}
		}
		
		if(dp[n][(1 << n) - 1][0] == inf && dp[n][(1 << n) - 1][1] == inf) return -1;
		vi ans;
		int i = n, j = (1 << n) - 1, s = abs(dp[n][(1 << n) - 1][0]) > abs(dp[n][(1 << n) - 1][1]);
		while(i > 0){
			ans.pb(d[prev[i][j][s] / 2]);
			int ns = prev[i][j][s] % 2;
			j &= ~(1 << prev[i--][j][s] / 2);
			s = ns;
		}
		ll res = 0;
		rep(i, n) res *= 10, res += ans[n - i - 1];
		return res;
	}
};