zoukankan      html  css  js  c++  java
  • 洛谷 P5162 WD与积木 解题报告

    P5162 WD与积木

    题目背景

    WD整日沉浸在积木中,无法自拔……

    题目描述

    WD想买(n)块积木,商场中每块积木的高度都是(1),俯视图为正方形(边长不一定相同)。由于一些特殊原因,商家会给每个积木随机一个大小并标号,发给WD。

    接下来WD会把相同大小的积木放在一层,并把所有层从大到小堆起来。WD希望知道所有不同的堆法中层数的期望。两种堆法不同当且仅当某个积木在两种堆法中处于不同的层中,由于WD只关心积木的相对大小,因此所有堆法等概率出现,而不是随机的大小等概率(可以看样例理解)。输出结果(mod 998244353)即可。

    (如果还是不能够理解题意,请看样例)

    输入输出格式

    输入格式:

    第一行一个数(T),表示询问个数。

    接下来(T)行每行一个数(n),表示WD希望使用(n)块积木。

    输出格式:

    (T)行,每行一个数表示答案(mod 998244353)

    说明

    subtask1(21pts): (1≤T≤1,000, 1≤n≤1,000)

    subtask2(37pts):(~1le Tle 10,~1le nle 100,000)

    subtask3(42pts):(~1le Tle 100,000,~1le nle 100,000)


    当时拿斯特林数推了一会儿没推出来就走了,原来只是部分分啊。

    姿势水平屑了,今天稍微给自己普及了一些指数型生成函数。

    朴素DP

    (g_n)代表(n)个东西的堆法,就是有标号球和盒子不准空的方案数,就是有序贝尔数,递推的时候枚举第一堆大小。

    [g_n=sum_{i=1}^n inom{n}{i}g_{n-i} ]

    (f_i)代表(n)个东西所有堆法的总层数。

    [f_n=g_n+sum_{i=1}^ninom{n}{i}f_{n-i} ]

    从意义上看,(f_0=0,g_0=1),不想从意义上看就稍微推一下。

    实质还是枚举第一堆的大小,(g_n)算的是枚举的这一堆的贡献和,剩下的是其他堆的贡献。

    然后构造指数生成函数

    [F(x)=sum_{i=0}^inftyfrac{f_i}{i!}x^i,G(x)=sum_{i=0}^inftyfrac{g_i}{i!}x^i,H(x)=sum_{i=0}^inftyfrac{1}{i!}x^i ]

    然后稍微注意一下发现上面的下标是从(1)开始的,于是直接卷会多一个

    [2G=GH+1,2F=FH+G-1 ]

    关于+1-1,今天想到一种理解方式。

    (1)实际上是一个多项式单位元,像数论卷积定义的(epsilon(n)=[n=1])那样。

    然后+1还是为了(g_0=1)考虑的,因为本身这个东西已经封闭了,你得给它打开一个开口啊(天呐我在扯什么

    后面-1减的实际上还是(g_0=1)

    化简一下式子

    [F=G(G-1) ]

    求逆求出(G)就行了


    Code:

    #include <cstdio>
    #include <algorithm>
    const int mod=998244353,Gi=332748118;
    const int M=(1<<18)+10;
    const int N=1e5;
    #define mul(a,b) (1ll*(a)*(b)%mod)
    #define add(a,b) ((a+b)%mod)
    int qp(int d,int k){int f=1;while(k){if(k&1)f=mul(f,d);d=mul(d,d),k>>=1;}return f;}
    int fac[M],inv[M],ans[M],H[M],A[M],B[M],D[N],turn[M],len=1;
    void NTT(int *a,int typ,int len)
    {
        int L=-1;for(int i=1;i<len;i<<=1) ++L;
        for(int i=1;i<len;i++)
        {
            turn[i]=turn[i>>1]>>1|(i&1)<<L;
            if(i<turn[i]) std::swap(a[i],a[turn[i]]);
        }
        for(int le=1;le<len;le<<=1)
        {
            int wn=qp(typ?3:Gi,(mod-1)/(le<<1));
            for(int p=0;p<len;p+=le<<1)
            {
                int w=1;
                for(int i=p;i<p+le;i++,w=mul(w,wn))
                {
                    int tx=a[i],ty=mul(w,a[i+le]);
                    a[i]=add(tx,ty);
                    a[i+le]=add(tx,mod-ty);
                }
            }
        }
        if(!typ)
        {
            int inv=qp(len,mod-2);
            for(int i=0;i<len;i++) a[i]=mul(a[i],inv);
        }
    }
    void polyinv(int *a,int *b,int len)
    {
        if(len==1){b[0]=qp(a[0],mod-2);return;}
        polyinv(a,b,len>>1);
        for(int i=0;i<len;i++) A[i]=b[i],B[i]=a[i],A[i+len]=B[i+len]=0;
        NTT(A,1,len<<1),NTT(B,1,len<<1);
        for(int i=0;i<len<<1;i++) A[i]=mul(A[i],add(2,mod-mul(A[i],B[i])));
        NTT(A,0,len<<1);
        for(int i=0;i<len;i++) b[i]=A[i];
    }
    int main()
    {
        fac[0]=1;for(int i=1;i<=N;i++) fac[i]=mul(fac[i-1],i);
        inv[N]=qp(fac[N],mod-2);
        for(int i=N-1;~i;i--) inv[i]=mul(inv[i+1],i+1);
        for(int i=0;i<=N;i++) H[i]=mod-inv[i];H[0]=1;
        while(len<=N) len<<=1;
        polyinv(H,ans,len);
        for(int i=0;i<len;i++) D[i]=mul(ans[i],fac[i]),H[i]=ans[i];
        --ans[0];
        NTT(ans,1,len<<1),NTT(H,1,len<<1);
        for(int i=0;i<len<<1;i++) ans[i]=mul(ans[i],H[i]);
        NTT(ans,0,len<<1);
        for(int i=0;i<=N;i++) ans[i]=mul(qp(D[i],mod-2),mul(ans[i],fac[i]));
        int T,n;scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n);
            printf("%d
    ",ans[n]);
        }
        return 0;
    }
    

    2018.12.31

  • 相关阅读:
    centos7的变化(转)
    配置邮件报警功能(脚本方式)
    临时和永久关闭Selinux
    centos7.2安装apache比较简单,直接上代码
    zabbix--------配置邮件报警功能---服务器上配置---------
    初来驾到学java修饰符的使用
    面向对象小小理解
    出来驾到学java3
    出来驾到学java2
    初来驾到学JAVA
  • 原文地址:https://www.cnblogs.com/butterflydew/p/10202982.html
Copyright © 2011-2022 走看看