kmjp's blog

競技プログラミング参加記です

TopCoderOpen 2016 Round2B Medium FoxAndGemstone

これは思いつかんなぁ…。
https://community.topcoder.com/stat?c=problem_statement&pm=14296

問題

N個のバッグがある。
各バッグには、A~Pの計16種類の宝石がそれぞれいくつか入っている。

同じ種類の宝石は同じ重さで、異なる種類の宝石は互いに異なっていることが分かっている。

ここで、宝石を両皿に1個ずつ載せられる天秤量りがあるとする。
これにより、宝石の重さの絶対値はわからないが、宝石の大小関係は判明する。
宝石の重さの大小関係が判明したとき、どのバッグが一番重いか必ず判定することは可能かどうか答えよ。

解法

内容物が同じバッグは取り除こう。
宝石の種類数P=16とする。
条件を満たすには、宝石の重さの大小関係16!通りについて、それぞれどのバッグが一番重いか判定できれば良い。
逆にいうと、各バッグが最も重くなるケースを数え上げ、その和が16!であれば条件を満たす。

各バッグが最も重くなる組み合わせは、bitDPで数え上げることができる。
各バッグが最も重くなる条件は、宝石の重さを重い順に並べたとき、その任意のprefixについて、各バッグそのprefixに含まれる宝石の個数を数えたとき、最も重いバッグ中の宝石が(同着で良いので)最大であることである。
これは各prefixをbitmaskであらわすことで、bitDP出来る。

1点注意点として、bitmaskに対応する宝石の個数は前処理で先に数え上げておこう。
BitDP中に行うと計算量がO(2^P*P*N^2)になりTLEする。

int num[51][16];
int num2[51][1<<16];
ll dp[1<<16];

class FoxAndGemstone {
	public:
	string isPossible(vector <string> B) {
		int i,x,y,mask;
		
		FORR(s,B) sort(s.begin(),s.end());
		sort(B.begin(),B.end());
		B.erase(unique(B.begin(),B.end()),B.end());
		int N=B.size();
		ZERO(num);
		ZERO(num2);
		FOR(i,N) {
			FORR(r,B[i]) num[i][r-'A']++;
			FOR(x,1<<16) FOR(y,16) if(x&(1<<y)) num2[i][x]+=num[i][y];
		}
		
		ll tot=0;
		FOR(x,N) {
			ZERO(dp);
			dp[0]=1;
			
			for(int mask=1;mask<1<<16;mask++) {
				FOR(y,N) if(num2[y][mask]>num2[x][mask]) break;
				if(y<N) continue;
				FOR(y,16) if(mask&(1<<y)) dp[mask] += dp[mask^(1<<y)];
			}
			tot+=dp[(1<<16)-1];
		}
		
		if(tot==16LL*15*14*13*12*11*10*9*8*7*6*5*4*3*2) return "Possible";
		return "Impossible";
		
		
	}
}

まとめ

よくこんな問題考えるなぁ。