奖励卡
jzoj 3937
题目大意
现在有一场比赛,想观看的人要提交申请,现在有x个人提交后得到两个号,y个人提交后得到一个号,有n轮抽号,每一轮抽一个号(概率相等),这个号的所有者不参与下一轮抽号(即他的所有号消掉),现在问你:你拿一个号和拿两个号抽中的概率(你不算在前面的x,y中,写得很乱,见谅)
输入样例
输入样例#1
1 1 2
输入样例#2
10 10 10
输出样例
输出样例#1
0.3333333333333333
0.2
输出样例#2
0.5870875690480144
0.3640355515319861
输出要求
答案误差不得超过 1 0 − 9 10^{-9} 10−9。
数据范围
对于 10%的数据,
n
⩽
2
。
n\leqslant 2。
n⩽2。
对于 20%的数据,
n
⩽
3
。
n\leqslant 3。
n⩽3。
对于 30%的数据,
n
⩽
4
,
a
,
b
⩽
4
。
n\leqslant 4,a,b\leqslant 4。
n⩽4,a,b⩽4。
对于 60%的数据,
n
⩽
3000
,
a
,
b
⩽
2000
。
n\leqslant 3000,a,b\leqslant 2000。
n⩽3000,a,b⩽2000。
对于 100%的数据,
1
⩽
n
⩽
3000
,
0
⩽
a
,
b
⩽
1
0
9
。
1\leqslant n\leqslant 3000,0\leqslant a,b\leqslant 10^9。
1⩽n⩽3000,0⩽a,b⩽109。
解题思路
因为n有3000,a、b有
1
0
9
10^9
109,所以我们不可能枚举每一轮抽到谁
那我们可以想到
D
P
DP
DP
我们设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为第
i
i
i轮为止有
j
j
j个有两个号的人中了且你还没中的概率
由
f
[
i
]
[
j
]
f[i][j]
f[i][j]转移的状态有三个:
f
[
i
]
[
j
]
f[i][j]
f[i][j],
f
[
i
]
[
j
+
1
]
f[i][j+1]
f[i][j+1]和你中了
然后分开转移即可
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n, a, b;
double x, y, ans, f[3050][3050];
int main()
{
scanf("%d%d%d", &n, &a, &b);
a++; //先处理拿两个号的情况
f[0][0] = 1;//100%
for (int i = 0; i < min(n, a + b); ++i)//要保证不会回合比人数多
for (int j = 0; j <= min(a, i); ++j)//要保证中了的人数要小于回合数
{
x = (double)(a - j);//a中中了j个,剩下的(a-j)个
y = (double)(b - (i - j));//i回合,a中中了j个,b中中了(i-j)个,剩下(b-(i-j))个
f[i + 1][j + 1] += f[i][j] * ((x - 1) * 2) / (x * 2 + y);//(x*2+y)个号,有((x-1)*2)个号不是你的且是a类剩下的
f[i + 1][j] += f[i][j] * y / (x * 2 + y);//y个人是b类剩下的
ans += f[i][j] * 2 / (x * 2 + y);//你有两张票
}
printf("%.16g\n", ans);
a--;//减去原先的
b++;//现在枚举你有一张票
ans = 0;
memset(f, 0, sizeof(f));
f[0][0] = 1;
for (int i = 0; i < min(n, a + b); ++i)
for (int j = 0; j <= min(a, i); ++j)
{
x = (double)(a - j);
y = (double)(b - (i - j));
f[i + 1][j + 1] += f[i][j] * (x * 2) / (x * 2 + y);
f[i + 1][j] += f[i][j] * (y - 1) / (x * 2 + y);//(y-1)个号不是你的切实b类剩下的
ans += f[i][j] / (x * 2 + y);//你只有一个号
}
printf("%.16g", ans);
return 0;
}