kmjp's blog

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

Autumn Fest 2012 : C Cards

さて3問目。
ここまでは本番に何とか解けた。
http://autumn_fest.contest.atcoder.jp/tasks/autumn_fest_03


カードゲームをしてスコアの期待値を求める問題。
2枚裏返す→1枚表に戻す、と表裏が行き来するのが独特。
とはいえ、裏返し→表返しで合計1枚裏になるので、いずれ終了する。

この問題では、2枚選んで裏返す場合と、1枚表に戻す場合を交互にメモ化再帰させることになる。
関数の引数はカードの表裏の状態をbitmapで表したもの。
この問題はN<=20なので、2^20回の計算で済むので計算量は問題ない。

2枚裏返す処理では、今表のものから2枚選ぶケースを列挙し、それぞれのスコアの平均をとる。
1枚表に戻す処理では、裏のものから1枚選ぶケースを列挙し、それぞれのスコアの平均をとる。
全部表の状態から、1枚除き裏になるまで上の2パターンを繰り返せばよい。

int N;
int X[30];

double OM[1<<20];
double UR[1<<20];

int bitcount(int n) {
	int i=0;
	while(n>0) {i += n%2; n>>=1;}
	return i;
}

double ura(int mask) ;
double omote(int mask) {
	int x,y,c;
	double t;
	if(OM[mask]<0) {
		if(bitcount(mask)>=N-1) {
			OM[mask]=0;
		}
		else {
			t=c=0;
			FOR(x,N) {
				if(mask & (1<<x)) continue;
				for(y=x+1;y<N;y++) {
					if(mask & (1<<y)) continue;
					
					c++;
					t += ura(mask | ((1<<x)|(1<<y)));
				}
			}
			OM[mask] = t/c;
		}
	}
	return OM[mask];
}
double ura(int mask) {
	int i,c;
	double t;
	if(UR[mask]<0) {
		t=c=0;
		FOR(i,N) {
			if(mask & (1<<i)) {
				c++;
				t += X[i]+omote((mask ^ (1<<i)));
			}
		}
		if(c==0) UR[mask]=0;
		else UR[mask] = t/c;
	}
	return UR[mask];
}

void solve() {
	int x,y,i,j,p,rr,TM,TTC;
	
	N=GETi();
	FOR(i,N) X[i]=GETi();
	FOR(i,1<<20) {
		OM[i]=UR[i]=-1;
	}
	
	_P("%.9f\n",omote(0));
	return;
}

まとめ

2つの再帰を繰り返すってのが面白いね。
これ、カードが表裏行ったり来たりするけどうまくDPで書けるのかな?

GCJ 2009でもこういう問題あったよね。
http://code.google.com/codejam/contest/188266/dashboard#s=p2