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

题目描述

传送门

题目大意:一个口袋中装有巧克力,巧克力的颜色有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)cmCmc2ccn xn 项的系数乘以 n!
这里补充一点, ekx xn 的系数为 knn! ,这个吧我其实不会证明,会了再来填坑。
现在考虑如果将 (exex)m(ex+ex)cm 展开得到每个 ekx xn 的系数 gn
a=ex,b=ex ,那么根据二项式定理
(ab)m=mr=0(1)rCrnanrbr , (a+b)m=mr=0Crnanrbr
因为x,-x会互相抵消,所以我们枚举 (exex)m ex 的指数i,那么 ex 的指数为m-i,再枚举 (ex+ex)cm ex 的指数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
#include
#include
#include
#include
#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);
    }
}

你可能感兴趣的:(数论,生成函数)