poj 1322 Chocolate (生成函数||概率DP)

来源:互联网 发布:知乎电子书 kindle 编辑:程序博客网 时间:2024/05/17 11:34

题目描述

传送门

题目大意:一个口袋中装有巧克力,巧克力的颜色有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[i1][j1]c(j1)c+f[i1][j+1]j+1c
但是这样做的时间复杂度太高了,那么有没有更高效的方法呢?其实是有的,那就是生成函数。
鉴于这是第一道生成函数的题,所以接下来先说明一些生成函数的预备知识。
指数型生成函数一般用来解决排列问题.定义一个序列{ai}的生成函数为

a0+a11!x+a22!x2+a33!x3+.......

指数型生成函数的常见形式
(1)序列<1,1,1,….,1> G(x)=1+x1!+x22!+x33!+..... 的指数型生成函数闭形式为ex
(2)序列<1,-1,1,-1,….> 的指数型生成函数闭形式为ex
(3)序列<1,0,1,0,1,….> 的指数型生成函数闭形式为ex+ex2
(4)序列<0,1,0,1,0….> 的指数型生成函数闭形式为exex2
(5)设序列{an}{bn}的指数生成函数是A(x),B(x) ,则C(x)=A(x)B(x)=n=0cnxnn!,其中cn=k=0akbnkC(n,k)
(6)g(x)=C0m+C1mx+C2mx2+.....=(1+x)m

然后回到这道题的题目。这道题时间上就是让m种颜色取奇数个,c-m种颜色取偶数个,求排列的个数。
根据上面的预备知识我们知道,奇数项指数生成函数为exex2,偶数项生成函数为ex+ex2,因此选m个奇数,c-m个偶数的指数生成函数为(exex)m(ex+ex)cm2c,由于哪m种颜色取奇数个是不确定的,所以方案数还要乘上Cmc,然后再除以总方案数cn就是概率。
本题的答案就是多项式(exex)m(ex+ex)cmCmc2ccnxn项的系数乘以n!
这里补充一点,ekxxn的系数为knn!,这个吧我其实不会证明,会了再来填坑。
现在考虑如果将(exex)m(ex+ex)cm展开得到每个ekxxn的系数gn
a=ex,b=ex,那么根据二项式定理
(ab)m=mr=0(1)rCrnanrbr,(a+b)m=mr=0Crnanrbr
因为x,-x会互相抵消,所以我们枚举(exex)mex的指数i,那么ex的指数为m-i,再枚举(ex+ex)cmex的指数j,那么ex的指数为c-m-j,由这两项合并得到的ekx中k的值为2(i+j)c,然后根据mi的奇偶性确定正负,对答案的贡献就是knn!CimCjcm(1)mi,我们发现外层还有一个n!cn,可以约分得到(kc)nCimCjcm(1)mi.
如果T=(kc)nCimCjcm(1)mi, 那么答案就是TCmc2c

代码

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define N 100using 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);    }}
0 0
原创粉丝点击