TopCoder SRM 572 Div1 Meidum EllysBulls

問題

一人がn桁の数字を思い浮かべ、もう一人がその数字を当てるゲームをする。
(leading zeroがあってもよい)
当てるほうは、n桁の数字をm個言う。
それぞれに対して、自分の数字と言われた数字で、一致している桁数を答える。


言った数字と、そのときに答えられた数が与えられるとき、
元の数が一意に定まるならその数を、
複数個候補があるならAmbiguityを、
候補が一つもないならLiarを返せ。

制約条件

n≦9
m≦50

方針

半分全列挙。
数字の前半を列挙する。前半部分で一致する個数から、
後半部分で一致しなければならないvectorが作れる。これをmapなどにいれておく。


数字の後半部分も列挙する。
後半部分の一致する数のvectorを作り、最初に作ったmapから引く。
すると計算量O(10^n/2 * n^2 * m)くらいで答えが求まる。


探索でも出来るらしい。

ソースコード

class EllysBulls {
	public:
	string getNumber(vector <string> guesses, vector <int> bulls) {
		int n = guesses[0].size();
		int m = guesses.size();
		int ten[20] = {};
		string ans;
		map<vi, int> memo;
		map<vi, string> memo2;
		char buf[100], fmt[100], fmt2[100];
		sprintf(fmt, "%%0%dd", n / 2);
		sprintf(fmt2, "%%0%dd", n - n / 2);
		
		ten[0] = 1;
		rep(i, 19) ten[i + 1] = ten[i] * 10;
		rep(k, ten[n / 2]){
			sprintf(buf, fmt, k);
			vi hit = bulls;
			rep(i, m) rep(j, n / 2) if(guesses[i][j] == buf[j]) hit[i]--;
			memo[hit]++;
			memo2[hit] = buf;
		}
		int nn = n - n / 2;
		rep(k, ten[nn]){
			sprintf(buf, fmt2, k);
			vi hit(m);
			rep(i, m) for(int j = n / 2; j < n; j++) if(guesses[i][j] == buf[j - n / 2]) hit[i]++;
			if(memo[hit] > 1) return "Ambiguity";
			if(memo[hit] == 1){
				if(!ans.empty()) return "Ambiguity";
				ans = memo2[hit] + buf;
			}
		}
		return ans.empty() ? "Liar" : ans;
	}
};

追記

探索のコード。
hitが多いやつから、当たっている桁を決め付ける。
hitが全て0になったらその時点で探索は終了できる。


当たっている桁が1ずつ減っていくときおそらく計算量が最悪で、
9!回分岐があることになるが、なんか速い。
最悪ケース600msくらい。
2〜3個あるだけで、他はほぼ1msくらいで終わる。

int n, m;
string ans;
vector<string> g;
void rec(int num, string &s, vi &b, ll use){
	if(ans[0] == 'A') return;
	if(num == (1 << n) - 1){
		rep(i, m) if(b[i]) return;
		if(ans.empty()) ans = s;
		else ans = "Ambiguity";
		return;
	}
	int mx = 0, mxi = 0;
	rep(i, m) if(!(use >> i & 1) && b[i] > mx){
		mx = b[i];
		mxi = i;
	}
	if(mx == 0){
		//hitが全部0だったら、もう再帰しなくてよい。
		//まだ決まってない桁を消去法で決めるしかない。
		string ss = s;
		bool amb = 0;
		rep(i, n) if(!(num & 1 << i)){
			int f = 0;
			rep(j, m) f |= 1 << g[j][i] - '0';
			int pc = __builtin_popcount(f);
			if(pc > 9) return;
			if(pc < 9) amb = 1;
			rep(j, 10) if(!(f & 1 << j)) ss[i] = '0' + j;
		}
		if(!amb && ans.empty()) ans = ss;
		else ans = "Ambiguity";
		return;
	}
	int comb = (1 << mx) - 1;
	//大きさmxの部分集合を列挙
	while(comb < 1 << n){
		int c1 = comb & -comb, c2 = comb + c1;
		string ss = s;
		vi bb = b;
		//すでに見た桁とかぶっている場合は失敗。
		//bはまだみてない桁のうち、当たっているものの個数なので。
		if(num & comb) goto FAIL;

		//他のbを減らす。0未満になってたら失敗。
		rep(i, n) if(!(num & 1 << i) && (comb & 1 << i)){
			int d = g[mxi][i] - '0';
			rep(j, m) if(g[j][i] - '0' == d){
				if(--bb[j] < 0) goto FAIL;
			}
			ss[i] = d + '0';
		}
		rec(num | comb, ss, bb, use | 1ll << mxi);
		if(ans[0] == 'A') return;
		FAIL:;
		comb = ((comb & ~c2) / c1 >> 1) | c2;
	}
}

class EllysBulls {
	public:
	string getNumber(vector <string> guesses, vector <int> bulls) {
		g = guesses;
		n = g[0].size();
		m = g.size();
		ans = "";
		
		string s(n, '.');
		rec(0, s, bulls, 0);
		return ans.empty() ? "Liar" : ans;
	}
};

追記2

フォーマット指定子のパラメータ自体に変数を使いたいとき、
つまり%04dみたいなの4の部分とかを変数で指定したとき、
sprintf(buf, %0*d, width, num);みたいな書き方ができるらしい。
https://twitter.com/k_hanazuki/status/309330515100573696