题意:
有一个\(n * m\)的棋盘,有一个人每天随机再一个空位上放一个棋子,问空棋盘变成每一行每一列都至少有一枚棋子的棋盘的期望天数是多少?

思路:
\(dp[i][j][k]\)表示\(i\)行至少有一枚棋子,\(j\)列至少有一枚棋子,已经放了\(k\)枚棋子的概率是多少.

那么\(dp[i][j][k]\)可从以下四种状态转移过来:

  1. \(dp[i - 1][j][k - 1]\),可能性为\((n - i + 1) * j / (sum - k + 1)\)
  2. \(dp[i][j - 1][k - 1]\),可能性为\((m - j + 1) * i / (sum - k + 1)\)
  3. \(dp[i - 1][j - 1][k - 1]\),可能性为\((n - i + 1) * (m - j +1) / (sum - k + 1)\)
  4. \(dp[i][j][k - 1]\),可能性为\((i * j - k + 1) / (sum - k + 1)\)
    注意第四种状态在\(i == n \&\& j == m\)的时候不要再转移了,因为根据题意,这个状态为终止状态。
    上式中的\(sum = n * m\)



#include <bits/stdc++.h>
using namespace std;

#define db double
#define N 60
db f[N][N][N * N];

int main()
{
	int T; cin >> T;
	while (T--)
	{
		int n, m; scanf("%d%d", &n, &m);
		if (n > m) swap(n, m);
		int sum = n * m;
		f[0][0][0] = 1;    
		for (int i = 0; i <= n; ++i)
			for (int j = 0; j <= m; ++j) if (i + j) 
				for (int k = 1; k <= sum; ++k)
				{
					db tot;
					if (i == n && j == m)
						tot = 0;
					else
						tot = f[i][j][k - 1] * (i * j - k + 1) * 1.0 / (sum - k + 1);	
					if (i - 1 >= 0)
						tot += f[i - 1][j][k - 1] * (n - i + 1) * j * 1.0 / (sum - k + 1);
					if (j - 1 >= 0)
						tot += f[i][j - 1][k - 1] * (m - j + 1) * i * 1.0 / (sum - k + 1);
					if (i - 1 >= 0 && j - 1 >= 0)
						tot += f[i - 1][j - 1][k - 1] * (n - i + 1) * (m - j + 1) * 1.0 / (sum - k + 1);
					f[i][j][k] = tot;
				}
		db res = 0;
		for (int i = 1; i <= sum; ++i)
			res += f[n][m][i] * i;
		printf("%.10f\n", res);
	}
	return 0;
}