kmjp's blog

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

TopCoder SRM 642 Div1 Hard WheelofFortune

時間かかったけど自力で解けた。800ptだしね。
http://community.topcoder.com/stat?c=problem_statement&pm=13502

問題

N個のセクタに分かれた円形の車輪を使ってゲームを行う。
各セクタのスコアは初期状態で0である。

この車輪に対し、M回スコアの追加を行う。
各追加はパラメータs[i]で表される。
これは、車輪のうち等確率でランダムにどこか連続したs[i]個のセクタのスコアを1ポイントインクリメントする。

この状態の車輪に対し、プレイヤーは2つのセクタを選択すると、そのスコアの合計が取得スコアとなる。
スコア追加後の各セクタのスコアはプレイヤーには伝えられない。
ただし1つ目のセクタを選択すると、そのセクタのスコアを教えて貰うことができるので、その情報を参考に2つ目のセクタを選択できる。

プレイヤーが最適手を取る場合、プレイヤーの取得スコアの期待値を求めよ。

解法

1手目は何も情報がないので、どこのセクタを選択しても取得するスコアの期待値は同じである。
1回のスコア追加あたりs[i]/Nの確率で1ポイント得られる単純なDPで計算できる。

問題は1手目の情報をもとに2手目をどう選択するかである。
結論としては、状態として[2手目の位置][1手目のポイント]を持ち、2手目のポイントの期待値をDPで求めていけば良い。
2手目の各位置について、1手目をs[i]/Nの確率でポイントを得た場合と、(N-s[i])/Nの確率でポイントを得られなかった場合それぞれ、2手目でポイントを得られるかどうかを求め、重みづけ平均を求めていく。

あとは最後に、1手目の取得ポイント数ごとに、2手目の期待値が最高となる2手目の位置を選択して合計の期待値を求めていけば良い。

class WheelofFortune {
	public:
	double maxExpectedValue(int N, vector <int> s) {
		int i,x,y;
		int L=s.size();
		double dp[303]={},op[2][303][303];
		
		ZERO(op);
		dp[0]=1;
		
		FOR(i,L) {
			int cur=i%2,tar=cur^1;
			ZERO(op[tar]);
			double p=s[i]*1.0/N;
			
			for(y=1;y<=N/2;y++) {
				double dp2[303]={};
				for(x=i+1;x>=0;x--) if(dp[x]) {
					if(s[i]==N) {
						dp2[x+1]+=dp[x];
						op[tar][y][x+1]+=dp[x]*(op[cur][y][x]+1);
					}
					else {
						dp2[x+1]+=dp[x]*p;
						dp2[x]+=dp[x]*(1-p);
						op[tar][y][x+1]+=dp[x]*    p*(op[cur][y][x]+(max(0,s[i]-y)+max(0,s[i]-(N-y)))*1.0/s[i]);
						op[tar][y][x]  +=dp[x]*(1-p)*(op[cur][y][x]+(1+min(y,N-s[i])-max(1,y-(s[i]-1)))*1.0/(N-s[i]));
					}
				}
				for(x=i+1;x>=0;x--) if(dp2[x]) op[tar][y][x] /= dp2[x];
			}
			for(x=i+1;x>=0;x--) {
				dp[x+1] += dp[x]*p;
				dp[x]   *= 1-p;
			}
		}
		
		double ret=0;
		FOR(i,L+1) {
			double ma=0;
			for(x=1;x<=N/2;x++) ma=max(ma,op[L%2][x][i]);
			ret+=dp[i]*(i+ma);
		}
		return ret;
	}
}

まとめ

なかなか複雑な状態遷移のDPだけど、「ある状態になる確率の期待値」と「得られるスコアの期待値」と異なる2値をDPしていくのが面白かった。