memo[両方の位置が特定されていない組みの数][片方の位置が特定されていない組みの数] = 期待値
全ての位置を記憶できるという事は、あるターンで最初にめくるカードはまだ一度もめくっていない位置を選ぶのが最適。
絵が合わなかった場合、もう片方が出るまではずっと放置することになる。
"初めてめくるカード" -> "初めてめくるカード" (運良く同じ絵)
"初めてめくるカード" -> "初めてめくるカード" (違う絵)
"初めてめくるカード" -> "もう片方の位置が分かるカード"
"もう片方の位置が分かるカード" -> "もう片方のカード"
の4パターンを考える。
const int H = 50 + 1;
const int W = 50 + 1;
const int P = H * W;
double memo[P][P];
double rec(int both, int half)
{
double& ret = memo[both][half];
if (ret != -1) return ret;
if (both == 0 && half == 0) return 0;
double sum = 0;
double closed = both * 2 + half;
double w = (closed - half) / closed;
if (1 <= both) { // hit
double p = 1.0 / (closed - 1.0) * w;
sum += (1.0 + rec(both - 1, half)) * p;
}
if (2 <= both) { // miss
double p = (closed - half - 2.0) / (closed - 1.0) * w;
sum += (1.0 + rec(both - 2, half + 2)) * p;
}
if (1 <= both && 1 <= half) { //
double p = 1.0 * half / (closed - 1.0) * w;
sum += (2.0 + rec(both - 1, half)) * p;
}
if (half) {
double p = 1.0 * half / closed;
sum += (1.0 + rec(both, half - 1)) * p;
}
return ret = sum;
}
class PerfectMemory {
public:
double getExpectation(int N, int M)
{
fill(&memo[0][0], &memo[P - 1][P - 1] + 1, -1);
return rec(N * M / 2, 0);
}