题目描述
传送门
题目大意:一个口袋中装有巧克力,巧克力的颜色有c种。现从口袋中取出一个巧克力,若取出的
巧克力与桌上已有巧克力颜色相同,则将两个巧克力都取走,否则将取出的巧克力放在桌上。
设从口袋中取出每种颜色的巧克力的概率均等。求取出 n 个巧克力后桌面上剩余 m 个巧克
力的概率。
题解
首先m的个数一定小于等于c,因为如果某种颜色的巧克力数量是大于等于2,那么一定会两个一对被取走,也就是最后剩下的每种巧克力要么只有一个要么没有。如果(n-m)%2!=0,或者m>n,m>n,那么概率一定为0.
那么这个问题其实就可以用概率与期望DP来解决。
令
f[i,j]
表示取出i块巧克力,恰好剩j块的概率。
f[i,j]=f[i−1][j−1]∗c−(j−1)c+f[i−1][j+1]∗j+1c
但是这样做的时间复杂度太高了,那么有没有更高效的方法呢?其实是有的,那就是生成函数。
鉴于这是第一道生成函数的题,所以接下来先说明一些生成函数的预备知识。
指数型生成函数一般用来解决排列问题.定义一个序列{ai}的生成函数为
指数型生成函数的常见形式
(1)序列<1,1,1,….,1> G(x)=1+x1!+x22!+x33!+..... 的指数型生成函数闭形式为 ex
(2)序列<1,-1,1,-1,….> 的指数型生成函数闭形式为 e−x
(3)序列<1,0,1,0,1,….> 的指数型生成函数闭形式为 ex+e−x2
(4)序列<0,1,0,1,0….> 的指数型生成函数闭形式为 ex−e−x2
(5)设序列 {an}{bn} 的指数生成函数是 A(x),B(x) ,则 C(x)=A(x)B(x)=∑∞n=0cn∗xnn! ,其中 cn=∑∞k=0ak∗bn−k∗C(n,k)
(6) g(x)=C0m+C1mx+C2mx2+.....=(1+x)m
然后回到这道题的题目。这道题时间上就是让m种颜色取奇数个,c-m种颜色取偶数个,求排列的个数。
根据上面的预备知识我们知道,奇数项指数生成函数为
ex−e−x2
,偶数项生成函数为
ex+e−x2
,因此选m个奇数,c-m个偶数的指数生成函数为
(ex−e−x)m(ex+e−x)c−m2c
,由于哪m种颜色取奇数个是不确定的,所以方案数还要乘上
Cmc
,然后再除以总方案数
cn
就是概率。
本题的答案就是多项式
(ex−e−x)m(ex+e−x)c−m∗Cmc2c∗cn
的
xn
项的系数乘以
n!
这里补充一点,
ekx
中
xn
的系数为
knn!
,这个吧我其实不会证明,会了再来填坑。
现在考虑如果将
(ex−e−x)m(ex+e−x)c−m
展开得到每个
ekx
中
xn
的系数
gn
。
设
a=ex,b=e−x
,那么根据二项式定理
(a−b)m=∑mr=0(−1)rCrn∗an−r∗br
,
(a+b)m=∑mr=0Crn∗an−r∗br
因为x,-x会互相抵消,所以我们枚举
(ex−e−x)m
中
ex
的指数i,那么
e−x
的指数为m-i,再枚举
(ex+e−x)c−m
中
ex
的指数j,那么
e−x
的指数为c-m-j,由这两项合并得到的
ekx
中k的值为
2∗(i+j)−c
,然后根据
m−i
的奇偶性确定正负,对答案的贡献就是
knn!∗Cim∗Cjc−m∗(−1)m−i
,我们发现外层还有一个
n!cn
,可以约分得到
(kc)n∗Cim∗Cjc−m∗(−1)m−i
.
如果
T=∑(kc)n∗Cim∗Cjc−m∗(−1)m−i
, 那么答案就是
T∗Cmc2c
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100
using namespace std;
double C[N+3][N+3];
int c,m,n;
double quickpow(double num,int x)
{
double ans=1; double base=num;
while (x) {
if (x&1) ans=ans*base;
x>>=1;
base=base*base;
}
return ans;
}
int main()
{
freopen("a.in","r",stdin);
for (int i=0;i<=N;i++) C[i][0]=1;
for (int i=1;i<=N;i++)
for (int j=1;j<=i;j++) C[i][j]=C[i-1][j-1]+C[i-1][j];
while (true) {
scanf("%d",&c);
if (!c) break;
scanf("%d%d",&n,&m);
if ((n-m)%2||m>c||m>n) {
printf("0.000\n");
continue;
}
double ans=0;
for (int i=0;i<=m;i++)
for (int j=0;j<=c-m;j++) {
double k=2.0*(i+j)-c;
if ((m-i)&1) ans-=quickpow(k*1.0/c,n)*C[m][i]*C[c-m][j];
else ans+=quickpow(k*1.0/c,n)*C[m][i]*C[c-m][j];
}
ans/=quickpow(2.0,c);
ans*=C[c][m];
printf("%.3lf\n",ans);
}
}