zoukankan      html  css  js  c++  java
  • 生成函数小结——[ EGF ][ ln 的一个套路 ][ 概率生成函数 ]

    看了jcvb的WC2015交流课件。虽然没懂后面的复合逆部分,但生成函数感觉受益良多。

    指数生成函数

      集合中大小为 i 的对象的权值是 ( a_i ) ,该集合的生成函数是 ( sumlimits_{i>=0} frac{a_i}{i!} x^i )

      一个重要式子: ( sumlimits_{i=0}^{infty} frac{A^i}{i!} = e^A ) 。其中 A 可以是一个多项式。

      对于有标号对象的计数。可以“拼接”,即 “大小为 i 的集合的带标号方案” 与 “大小为 j 的集合的带标号方案” ,想要维持相对大小地拼在一起变成 “大小为 i+j 的集合的带标号方案”。

      “拼接” 的方法就是直接把两个 EGF 乘在一起。

      一般来说,要取出 “有 n 个元素” 的方案,就是把 ( x^n ) 系数拿出来,乘上 n! 。这样就表示了组合数(因为集合的相对顺序无关,所以是组合数)。

      但在 “拼接” 的过程中,不用担心 n! 的部分,直接把只有 ( frac{1}{i!} ) 和 ( frac{1}{j!} ) 的部分乘起来,就是正确的。

      比如课件中举的例子:

      

      ( 2^{inom{n}{2}} ) 是 n 个点的简单无向图的方案数。直接乘了一个 ( frac{1}{n!} ) ,只看系数的话,是莫名其妙的东西。但是把 ( c_n ) 也乘上 ( frac{1}{n!} ) 作为 EGF 的系数,两者就可以直接 “拼接” 。最后的答案就是 ( C(x) ) 的 ( x^n ) 项系数再乘一个 ( n! ) 。

    多项式 ln 相关

      一个重要式子: ( sumlimits_{i>=1} frac{x^i}{i} = -ln(1-x) ) 

      更普遍的形式其实是: ( ln(1+x) = sumlimits_{i>=1}frac{(-1)^{i-1}}{i} x^i )

      与之相关的应用,这里举出课件上涉及的两个:

      1.置换计数

        轮换组成置换,适合用 EGF 的角度来看。

      最后那步就是 ( exp(-ln(1-x)) = exp(ln(frac{1}{1-x})) = frac{1}{1-x} = sumlimits_{i=0}^{infty}x^i = sumlimits_{i=0}^{infty}frac{i!}{i!}x^i )

      2.背包计数(无标号集合计数)

        

        课件给出了三个版本的问题,并给出了第一个版本的解答。第二个版本就是自己写的,可能有错误,欢迎指正。第三个版本不太会……

        

          不同种类算多种方案。

          写出生成函数:( prodlimits_{i=1}^{n} ( sumlimits_{j>=0}( x^{i*j} )^{a_i} )

                 (=prodlimits_{i=1}^{n} ( frac{1}{1-x^i} )^{a_i} )

                 (=exp( sumlimits_{i=1}^{n} -a_i * ln( 1-x^i ) ) )

                 (=exp( sumlimits_{i=1}^{n}a_i sumlimits_{j>=1}frac{x^{i*j}}{j} ) ) (这里用了那个式子)

                 (=exp( sumlimits_{j>=1} frac{1}{j} sumlimits_{i=1}^{n} a_i * x^{i*j} ) )

                 (=exp( sumlimits_{j>=1} frac{1}{j} A(x^j) ) )

          对于每个 j , A(x) 只有 j 的倍数次项的系数需要关注。复杂度可以做到调和级数 nlogn 。

        

          写出生成函数:( prodlimits_{i=1}^{n} ( 1+x^i )^{a_i} )

                 (=exp( sumlimits_{i=1}^{n} a_i * ln(1+x^i) ) )

                 (=exp( sumlimits_{i=1}^{n} a_i sumlimits_{j>=1} frac{(-1)^{j-1}}{j} x^{i*j} ) )

                 (=exp( sumlimits_{j>=1} frac{(-1)^{j-1}}{j} A(x^j) ) )

        

          写出生成函数:( prodlimits_{i=1}^{n} sumlimits_{j=0}^{a_i} x^{i*j} )

                 (=exp( sumlimits_{i=1}^{n} ln( frac{1-x^{j*(a_i+1)}}{1-x^i} ) ) )

          然后就不会了……

        这种背包计数的题,遇见两道,把自己的博客链接粘过来:

        https://www.cnblogs.com/Narh/p/10396644.html

        https://www.cnblogs.com/Narh/p/10405656.html

        当初做这两个题的时候,用的是另一个式子来推导的:( ( ln( f(x) ) )' = frac{f'(x)}{f(x)} )

        写成那样,分子就用多项式的样子写求导,即把指数搬下来;分母就是正常的式子,和分子乘一下,整个就变成一个好看的多项式的样子了。

        然后积分也是用多项式的样子写,把系数搬到指数上,推出来的结果就是一样的。

        不过不管是这个式子,还是这回介绍的那个式子,都不太明白为什么能这样等于过去……

    看了yml的2018集训队论文,学习了一下概率生成函数。不过没懂方差那部分。

    概率生成函数

      概率生成函数就是以 “离散变量 x = i 的概率” 为第 i 项系数的生成函数。

      考虑论文里涉及的三道例题。

      一. CTSC2006 歌唱王国

        题目:https://www.luogu.org/problemnew/show/P4548

        令 F(x) 表示序列在第 i 个位置结尾的概率。 G(x) 表示序列在第 i 个位置没有结尾的概率(的普通生成函数)。

        列方程:

            ( F(x)+G(x) = 1+G(x)*x )

            左边是第 i 个位置随便的方案。右边是第 i-1 个位置没有结尾,然后又填一个字符的方案。那么第 i 个位置就也是随便的。+1表示第 0 个位置,因为该位置没有 “第 i-1 个位置”。

            ( G(x)(frac{1}{m}x)^L = sumlimits_{i=1}^{L}a_i F(x) (frac{1}{m}x)^{L-i} )

            其中, ( a_i ) 表示 [ A[ 1,i ] = A[ len-i+1 , len ] ] ,(A是特定序列)。

            左边表示给第 i 个位置后面接上那个特定序列。这样它一定结束了。考察这 L 个接上的位置,发现在 L 的中间也可能已经结束。

            假如接了 i 个字符就结束,那么自己刚才填的是特定序列的前 i 个字符,结束表示这 i 个字符也是特定序列的后 i 个字符。所以有右边的式子。

        第一个方程,两边求导((a+b)' = a' + b'),得到 ( F'(x)+G'(x) = G'(x)*x + G(x) )

        因为要求的是 F'(1) ,所以把两个式子的 x 都代入 1 ,得到

            ( F'(1) = G(1) )

            ( G(1) = sumlimits_{i=1}^{L} a_i F(1) m^i ) (把左边的 ( (frac{1}{m}x)^L ) 放到右边)

        又有 F(1)=1 ,所以 ( F'(1) = sumlimits_{i=1}^{L} a_i m^i )

        那个 ( F'(1) = G(1) ) ,有 “ 长度的期望 = ( sumlimits_{i=0}^{infty} ) 在 i 处没有结尾的概率 ” 这样的理解。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    const int N=1e5+5,mod=1e4;
    int n,m,a[N],nxt[N],bin[N];
    int main()
    {
      m=rdn(); n=1e5;bin[0]=1;
      for(int i=1;i<=n;i++)
        bin[i]=bin[i-1]*m%mod;
      int T=rdn();
      while(T--)
        {
          n=rdn();for(int i=1;i<=n;i++)a[i]=rdn();
          for(int i=2;i<=n;i++)
        {
          int cr=nxt[i-1];
          while(cr&&a[cr+1]!=a[i])cr=nxt[cr];
          if(a[cr+1]==a[i])nxt[i]=cr+1;
          else nxt[i]=0;
        }
          int cr=nxt[n], ans=bin[n];
          while(cr)
        {
          ans=(ans+bin[cr])%mod; cr=nxt[cr];
        }
          printf("%04d
    ",ans);
        }
      return 0;
    }
    View Code

      二. SDOI2017 硬币游戏

        题目:https://www.luogu.org/problemnew/show/P3706

        令 ( F_j(x) ) 表示 i 位置,用串 j 结尾的概率生成函数。 G(x) 表示 i 位置还没结尾的概率。

        用理解列式子: ( sumlimits_{k=1}^{n} F_k(1) = 1 ) (用求导的那个方法推,也是一样)

        又有,对于第 k 个串: ( G(x)(frac{1}{2}x)^{L_k} = sumlimits_{i=1}^{L_k} sumlimits_{j=1}^{n} a_{k,j,i} ( frac{1}{2}x )^{L_k -i} )

            其中, ( a_{k,j,k} ) 表示 [ k 串的 i 长度前缀 = j 串的 i 长度后缀 ] 。

        即 ( G(1) = sumlimits_{i=1}^{L_k} sumlimits_{j=1}^{n} a_{k,j,i} 2^i f_j(1) )

        其实这样就可以高斯消元了!

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define ll long long
    #define db double
    using namespace std;
    const int N=305,b1=10009,m1=1e9+9,b2=10007,m2=993244853;
    int upt1(int x){while(x>=m1)x-=m1;while(x<0)x+=m1;return x;}
    int upt2(int x){while(x>=m2)x-=m2;while(x<0)x+=m2;return x;}
    
    int n,m,bn1[N],bn2[N],h1[N][N],h2[N][N];
    db bin[N],a[N][N],f[N]; char s[N];
    pair<int,int> get_h(int x,int l)
    {
      int a=upt1(h1[x][m]-(ll)bn1[m-l+1]*h1[x][l-1]%m1);
      int b=upt2(h2[x][m]-(ll)bn2[m-l+1]*h2[x][l-1]%m2);
      return make_pair(a,b);
    }
    void gauss(int n)
    {
      for(int i=1;i<=n;i++)
        {
          int cr=i;
          for(int j=i+1;j<=n;j++)
        if(fabs(a[j][i])>fabs(a[cr][i]))cr=j;
          //if(!a[cr][i])break;
          for(int j=i;j<=n+1;j++)swap(a[i][j],a[cr][j]);
          for(int j=i+1;j<=n;j++)
        {
          db sl=a[j][i]/a[i][i];
          for(int k=i;k<=n+1;k++) a[j][k]-=sl*a[i][k];
        }
        }
      for(int i=n;i;i--)
        {
          f[i]=a[i][n+1]/a[i][i];//// /a[i][i]
          for(int j=i-1;j;j--) a[j][n+1]-=f[i]*a[j][i];
        }
    }
    int main()
    {
      scanf("%d%d",&n,&m);
      bn1[0]=bn2[0]=1;
      for(int i=1;i<=m;i++)bn1[i]=(ll)bn1[i-1]*b1%m1;
      for(int i=1;i<=m;i++)bn2[i]=(ll)bn2[i-1]*b2%m2;
      for(int i=1;i<=n;i++)
        {
          scanf("%s",s+1);
          for(int j=1;j<=m;j++)
        {
          int w; if(s[j]=='H')w=1; else w=2;
          h1[i][j]=((ll)h1[i][j-1]*b1+w)%m1;
          h2[i][j]=((ll)h2[i][j-1]*b2+w)%m2;
        }
        }
      bin[0]=1;for(int i=1;i<=m;i++)bin[i]=bin[i-1]*2;
      for(int k=1;k<=n;k++)
        {
          for(int j=1;j<=n;j++)
        for(int i=1;i<=m;i++)
          if(make_pair(h1[k][i],h2[k][i])==get_h(j,m-i+1))
            a[k][j]+=bin[i];
          a[k][n+1]=-1;//G(1)
        }
      for(int i=1;i<=n;i++)a[n+1][i]=1; a[n+1][n+2]=1;
      gauss(n+1);
      for(int i=1;i<=n;i++)printf("%.10f
    ",f[i]);
      return 0;
    }
    View Code

        关于本题的另一种想法:https://www.cnblogs.com/Narh/p/10407245.html

       三. Dice

        题目:http://acm.hdu.edu.cn/showproblem.php?pid=4652

        题意:m 面的骰子。求:1.期望多少次,可以使得 “最后 n 次投出来的结果相同” ;2.期望多少次,可以是的 “最后 n ( n<=m ) 次投出来的结果互不相同” 。

        对于第一问,令 ( F_j(x) ) 表示以第 j 面重复 n 次结束的概率,( G(x) ) 表示不结束的概率。

          所有面等价,所以 ( sum F_j(1) = 1  ==>  F_j(1) = frac{1}{m} )

          又有 ( sum F'_j(1) = G(1) ) , ( G(1) ) 就是答案。

          ( G(x)( frac{1}{m}x )^n = sumlimits_{i=1}^{n} F_k(x) ( frac{1}{m}x )^{L-i} ) ,其中 k 是某一面。

          ( ==> G(1) = sumlimits_{i=1}^{n} F_k(x) m^i = sumlimits_{i=1}^{n} frac{1}{m}*m^i = sumlimits_{i=0}^{n} m^i )

        对于第二问,有 ( ( tot= inom{m}{n}*n! = frac{m!}{(m-n)!} ) ) 种结尾。令 ( F_j(x) ) 表示以第 j 种结尾结束的概率, ( G(x) ) 表示不结束的概率。

          所有结尾等价,所以 ( sum F_j(1) = 1 ==> F_j(1) = frac{(m-n)!}{m!} )

          又有 ( sum F'_j(1) = G(1) ) , ( G(1) ) 就是答案。

          ( G(x)(frac{1}{m}x)^n = sumlimits_{i=1}^{n} sumlimits_{j=1}^{tot} a_{k,j,i} F_j(x) (frac{1}{m}x)^{n-i} )

            其中, ( a_{k,j,i} ) 表示 [ 第 k 个结尾的前 i 个字符 == 第 j 个结尾的后 i 个字符 ] 。

          ( G(1) = sumlimits_{i=1}^{n} frac{(m-n)!}{m!} * m^i sumlimits_{j=1}^{tot} a_{k,j,i} )

            其实 ( sumlimits_{j=1}^{tot} a_{k,j,i} ) 是能算的。已知 i 个位置相等,剩下位置的方案就是 ( inom{m-i}{n-i} * (n-i)! = frac{(m-i)!}{(m-n)!} )

          ( G(1) = sumlimits_{i=1}^{n} frac{(m-i)!}{m!}*m^i = sumlimits_{i=1}^{n} frac{m^i}{m的i次下降幂} )

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define db double
    using namespace std;
    const int N=1e6+5;
    int op,n,m;
    int main()
    {
      int T; scanf("%d",&T);
      while(T--)
        {
          scanf("%d%d%d",&op,&m,&n);
          if(!op)
        {
          int ml=1,ans=0;
          for(int i=0;i<n;i++)
            { ans+=ml; if(i<n-1)ml*=m;}
          printf("%d
    ",ans);
        }
          else
        {
          db ans=0,ml=1;
          for(int i=1,j=m;i<=n;i++,j--)
            { ml=ml/j*m; ans+=ml;}
          printf("%.10f
    ",ans);
        }
        }
      return 0;
    }
    View Code
  • 相关阅读:
    使用 asp.net mvc和 jQuery UI 控件包
    ServiceStack.Redis 使用教程
    HTC T8878刷机手册
    Entity Framework CodeFirst 文章汇集
    2011年Mono发展历程
    日志管理实用程序LogExpert
    使用 NuGet 管理项目库
    WCF 4.0路由服务Routing Service
    精进不休 .NET 4.0 (1) asp.net 4.0 新特性之web.config的改进, ViewStateMode, ClientIDMode, EnablePersistedSelection, 控件的其它一些改进
    精进不休 .NET 4.0 (7) ADO.NET Entity Framework 4.0 新特性
  • 原文地址:https://www.cnblogs.com/Narh/p/10827124.html
Copyright © 2011-2022 走看看