假设我们已经确定了一个建造的顺序,那么如何计算按照这个顺序建造的方案数?这就相当于确定一个数组d,其中∑di<=X∑di<=X并且di>=max{hi,hi+1}di>=max{hi,hi+1},为了方便起见,记∑max{hi,hi+1}=S∑max{hi,hi+1}=S,d数组的个数显然就是CnX−1−S+nCX−1−S+nn(把X-1-S个苹果分给n+1个人,因为是小于等于X,所以要多一个人出来),现在,我们就只关心每一个S所对应的建造顺序的个数了。
可以用DP。可以利用建筑高度为1~n的排列的性质,从小到大的DP,这样,每次加入的max都是当前的高度。定义fi,j,kfi,j,k表示高度为1~i,S=j,还有k个位置需要插入建筑的方案数,fn,j,0fn,j,0就是S=j时的方案数。考虑如何转移,显然每次插入可以有三种情况,一种是两边都需要再插入,一种是只有一边需要再插入,还有一种是两边都不需要再插入,分类讨论一下。需要注意的是,这里之所以还要定义第三维,就是因为如果不能确定是否还需要再插入建筑的话,S的值就无法更新。最后还要注意一下插入两边端点的情况。
时间复杂度:O(n4)O(n4)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 100106
#define maxn 106
#define LL long long
using namespace std;
int n,X,P;
LL ans,inv[maxm],fact[maxm],f[2][maxn*maxn][maxn];
LL C(int x,int y){return fact[x]*inv[y]%P*inv[x-y]%P;}
int main(){
freopen("3.in","r",stdin);
freopen("3.out","w",stdout);
scanf("%d%d%d",&n,&X,&P);
inv[1]=1;
for(int i=2;i<=100100;i++)inv[i]=(LL)(P-P/i)%P*inv[P%i]%P;
for(int i=2;i<=100100;i++)(inv[i]*=inv[i-1])%=P;
fact[1]=1;
for(int i=2;i<=100100;i++)fact[i]=fact[i-1]*i%P;
f[1][0][0]=1;
for(int i=1;i<n;i++){
memset(f[(i&1)^1],0,sizeof(f[(i&1)^1]));
for(int j=0;j<=i*i;j++)
for(int k=0;k<i;k++) if(f[i&1][j][k]){
(f[(i&1)^1][j+2*(i+1)][k-1]+=f[i&1][j][k]*k%P)%=P;
(f[(i&1)^1][j][k+1]+=f[i&1][j][k]*k%P)%=P;
(f[(i&1)^1][j+i+1][k]+=f[i&1][j][k]*k%P*2%P)%=P;
(f[(i&1)^1][j+i+1][k]+=f[i&1][j][k]*2%P)%=P;
(f[(i&1)^1][j][k+1]+=f[i&1][j][k]*2%P)%=P;
}
}
for(int j=0;j<=n*n;j++)if(f[n&1][j][0])(ans+=C(X-j-1+n,n)*f[n&1][j][0]%P)%=P;
printf("%lld\n",ans);
return 0;
}