Particle

競技プログラミングについての雑記

POI 11th Guesswork

問題

http://main.edu.pl/en/archive/oi/11/zga
0と1の間の小数が9個来るから、小数が来る度に、全体の何番目か推測する。全部当たる確率をできるだけ大きくする。

解法

数学(確率(と組み合わせ)、積分)+DPで解いた。より具体的には、下に書いてあるようにして解いた。
TLは 25 sec であるが、毎回計算すると流石に遅いので、前計算した。これをそのまま投げると 8 sec くらいになる。vectorを使っているが、本当はlistを使うべき。

/*
 * Guesswork
 *
 * 解法: 最適戦略を用いたときの、n=1..kのときの勝率が分かると、n=k+1のときの最適戦略と勝率を求めることができる。
 * 最終的な勝率をP(n)とする。
 * 
 * 初めは、区間(0, 1)上に、n(=9)個の数が来るとする。
 * 区間(a, b)上に数cが来たら、区間(a, c)と区間(c, b)に分け、それぞれ数がいくつ来るか予想する。
 * 区間(a, b)上にk+1個の数が来るとしていた場合は、区間(a, c)、区間(c, b)上に合計k個の数が来るとして考える。
 * 以降、一次変換をして、(a, b) -> (0, 1)として考える。(変換後のcを改めてcとする)
 * それぞれl, k-l個来て、区間(0, 1)上に来た数に関しては正しく応答できる確率はk_C_l*P(l)*P(k)*c^l*(1-c)^(k-l)となる。
 * この確率が最大になるようなlは簡単に求めることができる(cが大きくなると、そのようなlも大きくなるから、隣と比較するだけでよい)
 * 区間(0, 1)上にcが来た時の勝率が分かったから、cについて積分すると、(k+1)個の数が来る時の勝率を求められる。
 */

#include "zga.h"
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<ld, ld> pdd;
typedef vector<pair<pdd, int> > vecp;
#define N 9

ll C[N][N];
long double P[N+1]; // P[9]は0.0333753になる
long double sep[N+1][N+1];

vecp rg; 

ld myPow(ld a, int r){
	ld res = 1.0;
	while(r){
		if(r&1) res*=a;
		a*=a;
		r>>=1;
	}
	return res;
}

ld calc_sep(int p, int q){
	ld A = (p+1)*P[p]*P[q], B = q*P[p+1]*P[q-1]; // 式変形すると、こうなる
	return A/(A+B);
}

ld integral(int p, int q){
	ld c = sep[p+q+1][p], d = sep[p+q+1][p+1];
	ld res = 0.0;
	for(int i = 0; i <= q; i++){
		res += C[i][q-i]*(myPow(d, p+i+1)-myPow(c, p+i+1))/(p+i+1)*(1-2*(i&1)); // 展開してから、積分
	}
	return res;
}

void inicjalizuj()
{
	for(int i = 0; i < N; i++){
		C[0][i] = C[i][0] = 1;
	}
	for(int i = 1; i < N; i++){
		for(int j = 1; j < N; j++){
			C[i][j] = C[i-1][j] + C[i][j-1];
		}
	}

	P[0] = 1.0;
	for(int i = 0; i < N; i++){
		for(int j = 0; j < i; j++){
			sep[i+1][j+1] = calc_sep(j, i-j);
		}
		sep[i+1][i+1] = 1.0;
		for(int j = 0; j <= i; j++){
			P[i+1] += C[j][i-j]*P[j]*P[i-j]*integral(j, i-j);
		}
	}
}

void nowa_rozgrywka ()
{
	rg = vecp(1, make_pair(make_pair(0, 1.0), N));
}

int kolejna_liczba (double x)
{
	int res = 1;
	for(vecp::iterator it = rg.begin(); it!=rg.end(); ++it){
		ld l = (*it).first.first, r = (*it).first.second;
		int num = (*it).second;
		if(l<=x && x<r){
			if(num==0) return 1;
			ld y = (x-l)/(r-l);
			int pos = 0;
			for(int i = 1; i <= num; i++){
				if(y<sep[num][i]){
					pos = i-1;
					break;
				}
			}
			res += pos;
			it = rg.erase(it);
			it = rg.insert(it, make_pair(make_pair(x, r), num-pos-1));
			rg.insert(it, make_pair(make_pair(l, x), pos));
			return res;
		}else{
			res += num+1;
		}
	}
	return 1;
}