题意简述
给定节点个数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<cstdio>
#include<cmath>
#include<cstring>
#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<N;j+=m){//蝴蝶操作
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 (N<mn){
N<<=1;
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;
}