由于每个盘子都有一个蛋糕/两个蛋糕/三个蛋糕,因此需要用状态压缩的思想来表示状态。
dp[i][j][k] --> 当前有i个盘子里有一个蛋糕,j个盘子里有两个蛋糕,k个盘子里有三个蛋糕将其全部吃完的期望次数。
设p1=i/n 为从这n个盘子中选出一个装有一个蛋糕的盘子的概率
p2=j/n 为从这n个盘子中选出一个装有两个蛋糕的盘子的概率
p2=k/n 为从这n个盘子中选出一个装有三个蛋糕的盘子的概率
p0=(n-(i+j+k))/n 为从这n个盘子中选出一个空盘子的概率
那么可以得到状态转移方程:
dp[i][j][k] = p1*dp[i-1][j][k] + p2*dp[i+1][j-1][k] + p3*dp[i][j+1][k-1] + p0*dp[i][j][k] + 1
将p0,p1,p2,p3代入并化简得
dp[i][j][k] = i/(i+j+k)*dp[i-1][j][k] + j/(i+j+k)*dp[i+1][j-1][k] + k/(i+j+k)*dp[i][j+1][k-1] + n/(i+j+k)
有了转移方程后通过记忆化搜索便可求解
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N=305;
double dp[N][N][N];
int vis[N][N][N];
int a[4],n;
double dfs(int i, int j, int k)
{
if(i<0 || j<0 || k<0)
return 0;
if(i==0 && j==0 && k==0)
return 0;
if(vis[i][j][k])
return dp[i][j][k];
double sum=i+j+k;
vis[i][j][k]=1;
return dp[i][j][k]=(k*dfs(i, j+1, k-1) + j*dfs(i+1, j-1, k) + i*dfs(i-1, j, k) + n)/sum;
}
int main()
{
int x;
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &x);
a[x]++;
}
printf("%.12lf\n", dfs(a[1], a[2], a[3])); // 注意小数开多一点
return 0;
}