SRM513 Div1 Medium(500) PerfectMemory
残っているカード枚数と、そのうち一度もめくっていないカードの枚数ごとに、最小手数の期待値を覚えておいて、動的計画法。
一度もめくっていないカードを最初にめくるのが最善で、1枚目が既にめくったシンボルの場合、2枚目はそのシンボルをめくれば良い。1枚目が既にめくったシンボルではない場合、2枚目は、1枚目と同じ、1枚目以外の既にめくったシンボルと同じ、2枚目も初めて見るシンボル、というパターンがある。
2枚目がに1枚目以外の既にめくったシンボルと同じ場合、その場でもう一度2枚目をめくって取るとすると、一度めくったカードの枚数は残っているカードの半分以下で、一度めくったカードのシンボルは全て異なるとすることができる。
#include <vector> using namespace std; class PerfectMemory{public: double getExpectation( int N, int M ) { int S = N*M; // [残っているカード][一度もめくっていないカード] vector<vector<double> > T( S+1, vector<double>(S+1) ); T[0][0] = 0; for ( int i=2; i<=S; i+=2 ) for ( int j=i/2; j<=i; j++ ) { T[i][j] = 0; double p = (double)(i-j)/j; // 1枚目がすでにめくったカードと同じシンボル T[i][j] += p * (T[i-2][j-1]+1); // 1枚目と2枚目が同じシンボル if ( j>=2 ) T[i][j] += (1-p) * (double)1/(j-1) * (T[i-2][j-2]+1); // 2枚目がすでにめくったカードと同じシンボル if ( j>=2 ) T[i][j] += (1-p) * (double)(i-j)/(j-1) * (T[i-2][j-2]+2); // 1枚目も2枚目もはずれ if ( j>=2 && j-2>=i/2 ) T[i][j] += (1-p) * (double)(j-(i-j)-2)/(j-1) * (T[i][j-2]+1); } return T[S][S]; }};