描述
题解
这个题是一个数论题,主要是推公式,但是 dp 同样是可以过的……
先来说 dp(代码 One),设置 dp[i][j][k][r] 表示 %3 后的数 1 出现了 i 次, 2 出现了 j 次,0 出现了 k 次,此时余数为 r 的概率,这样我们就可以轻松转移出来每一个状态了,具体状态转移看代码吧!
说到数论,这个题首先我们可以将所有数 %3 后发现, 0 和 2 都出现了 n 次,1 出现了 n+1 次,这里 0 对序列的余数状态无影响,所以我们最后考虑。
此时存在两种情况,以 1 打头、2 打头,分别考虑下来,
第一种的序列是 1121212…1212 ,这种情况,很明显,从头到尾都是唯一的,并且是合法的,这里的唯一说的是 %3 后的结果,酱紫,我们知道了每个数所能放的位置,所以只考虑 1 和 2 的计算结果为 n!∗(n+1)! ,接着我们可以考虑往其中插入 0 ,由于开头不能插,所以插零的方案数为 (2n+1)∗(2n+2)∗…∗3n,根据乘法原理,得到以 1 打头的情况数为:n!∗(n+1)!∗(2n+1)∗(2n+2)∗…∗3n。
第二种的序列是 2212121…2111 ,这种情况,我们会发现,当 2 使用完后,会紧接着放 111,而这里从第二个开始,就不合法了,所以以 2 打头的情况是无解的。
而所有的排列结果为 (3n+1)!,所以相除化简后:
n!(n+2)∗…∗2n∗(3n+1)
化简形式不唯一,考虑到代码的最简,化成下边的形式最好:
n!∗(n+1)(n+1)∗…∗2n∗(3n+1)
就酱,这个题算是告一段落了~~~
代码
One:
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 22;
int n;
double dp[MAXN][MAXN][MAXN][3];
int main()
{
int T;
cin >> T;
while (T--)
{
memset(dp, 0, sizeof(dp));
cin >> n;
int m = 3 * n + 1;
for (int i = 0; i <= n + 1; i++)
{
for (int j = 0; j <= n; j++)
{
for (int k = 0; k <= n; k++)
{
if (i + j + k == 1)
{
if (i)
{
dp[i][j][k][1] = (n + 1) * 1.0 / m;
}
else if (j)
{
dp[i][j][k][2] = n * 1.0 / m;
}
continue;
}
int now = m - i - j - k + 1;
dp[i][j][k][1] = (j == 0 ? 0 : dp[i][j - 1][k][2] * (n - j + 1) / now)
+ (k == 0 ? 0 : dp[i][j][k - 1][1] * (n - k + 1) / now);
dp[i][j][k][2] = (i == 0 ? 0 : dp[i - 1][j][k][1] * (n - i + 2) / now)
+ (k == 0 ? 0 : dp[i][j][k - 1][2] * (n - k + 1) / now);
}
}
}
printf("%.9lf\n", dp[n + 1][n][n][1]);
}
return 0;
}
Two:
#include <cstdio>
int n;
double ans;
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
ans = (n + 1.0) / (3 * n + 1);
for (int i = 1; i <= n; i++)
{
ans = ans * i / (i + n);
}
printf("%.9f\n", ans);
}
return 0;
}