VIJOS 1999 小h的妹子树三

题意简述

给定节点个数n和树高h(树根高为0),求出满足节点数位n,树高为h的二叉树的个数
答案对p取模,T组数据
对于100%的数据,满足1<=n<=1200,1<=h<=1200,1<=T<=2000000,1<=P<=1007。

分析

这题的数据范围提示我们要将所有询问预处理出来
而二叉树计数问题与卷积有着比较密切的联系,模数较小也提示我们这题正解很有可能就是FFT

设有n个节点,高度为h的二叉树个数为f[h,n]
先固定树根,假设左子树有x个节点,高度为h-1,那么右子树则需要放n-x-1个节点(树根用了1个节点),高度需要小于等于h-1
这种情况下方案数为f[h-1,x]*g[h-1,n-x-1] (g[h,n]表示高度不超过h节点数为n的二叉树个数)

而将两棵子树调换位置后依然是合法方案,因此上面的方案数还需要*2

那么不难列出dp方程:
f[h,n]=Σf[h-1,x]*g[h-1,n-1-x]*2
g[h,n]=g[h-1,n]+f[h,n]

但是这个dp方程是错误的

用这个方程计算出的答案会比实际答案多

这是因为在计算2*f[h-1,x]*g[h-1,n-x-1]时,计算结果中包括了2*f[h-1,x]*f[h-1,n-x-1]的部分(2份的左x,右n-x-1)。而在计算f[h-1,n-x-1]*g[h-1,x]时,计算结果中包括了f[h-1,n-x-1]*fh-1,x,与之前调换子树结果*2的计算重复统计了。因此f[h,n]的计算要去除这重复的一部分

那么可以得到修改后的dp方程:
f[h,n]=Σ(f[h-1,x]*g[h-2,n-1-x]*2+f[h-1,x]*f[h-1,n-1-x])
g[h,n]=g[h-1,n]+f[h,n]

f[h,n]的方程就是标准卷积形式了,可以直接套用FFT优化
由于要做h次FFT,因此时间复杂度为O(nhlog n)

由于这题的输入数据过于巨大,因此要使用读入优化

代码

#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const double pi=acos(-1),eps=1e-3;
using namespace std;
const int mn=1210;
int n,h,p,T,f[mn+10][mn+10],g[mn+10][mn+10],re[mn*4+10],bi,mh,N;//f,g定义如题解所述, re[x]表示x二进制反转后的数值 
typedef double LD;
struct Z{//复数计算定义 
    LD x,y;
    inline Z(LD X=0,LD Y=0){
        x=X,y=Y;
    }
};
inline Z operator +(const Z &a,const Z &b){
    return Z(a.x+b.x,a.y+b.y);
}
inline Z operator -(const Z &a,const Z &b){
    return Z(a.x-b.x,a.y-b.y);
}
inline Z operator *(const Z &a,const Z &b){
    return Z(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);
}
Z da[mn*4+10],db[mn*4+10],t[mn*4+10],w[mn*4+10];
void DFT(Z *a,int s){//求DFT 
    fo(i,0,N-1)
        t[i]=a[re[i]];
    int m2,bei;Z wi,y1,y2;
    for(int m=2;m<=N;m<<=1){
        m2=m>>1;bei=N/m;
        fo(i,0,m2-1){
            if (s>0)
                wi=w[i*bei];//虚根 
            if (s<0)
                wi=w[N-i*bei];
            for(int j=i;j//蝴蝶操作 
                y1=t[j],y2=t[j+m2]*wi;
                t[j]=y1+y2;
                t[j+m2]=y1-y2;
            }
        }
    }
    if (s==-1)
        fo(i,0,N-1)
            t[i].x=t[i].x/N;
    fo(i,0,N-1)
        a[i]=t[i];
}
char ch;
inline void scan(int &x){//读入优化 
    while(ch=getchar(),ch<'0'||ch>'9');x=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')x=x*10+ch-'0';
}
char cc[20];
int ct;
inline void print(int x){//输出优化 
    ct=0;
    while (x){
        cc[++ct]=x%10;
        x/=10;
    }
    for(int i=ct;i>0;i--)
        putchar(cc[i]+'0');
    if (!ct)
        putchar('0');
    putchar('\n');
}
int main(){
    scanf("%d%d",&T,&p);
    bi=0;N=1;
    while (N1;
        bi++;
    }
    N<<=1;bi++;
    fo(i,0,N-1){
        int te=i,u=0;
        fo(j,1,bi){
            u=(u<<1)+(te&1);
            te>>=1;
        }
        re[i]=u;
    }
    w[0]=Z(1,0);
    fo(i,1,N)
        w[i]=Z(cos(2*i*pi/N),sin(2*i*pi/N));//预处理虚根 
    f[0][0]=1;f[1][1]=1;g[0][0]=g[1][0]=g[1][1]=1;//dp边界 
    mh=1;
    while (T--){
        scan(n);scan(h);
        h++;
        if (h>mh){
            fo(i,mh+1,h){
                fo(j,0,mn){
                    da[j]=Z(f[i-1][j],0);
                    db[j]=Z(((g[i-2][j]<<1)+f[i-1][j])%p,0);
                }
                fo(j,mn+1,N-1){
                    da[j]=Z(0,0);
                    db[j]=Z(0,0);
                }
                DFT(da,1);
                DFT(db,1);
                fo(j,0,N-1)
                    da[j]=da[j]*db[j];
                DFT(da,-1);
                f[i][0]=0;
                g[i][0]=g[i-1][0];
                fo(j,1,mn){
                    f[i][j]=int(da[j-1].x+eps)%p;
                    g[i][j]=(g[i-1][j]+f[i][j])%p;
                }
            }
            mh=h;
        }
        print(f[h][n]);
    }
    return 0;
}

你可能感兴趣的:(vijos,FFT,数学,dp,vijos,FFT,树)