CF1580B Mathematics Curriculum
Link
Solution
一个值 \(x\) 的最大值个数就是它所在位置往前做单调栈,往后做单调栈后,两个单调栈的大小的和。这个东西对应于笛卡尔树中,\(x\) 所在位置的深度(考虑建树过程)。所以问题就转换为求第 \(m\) 层有 \(k\) 个节点的笛卡尔树有多少个。
可以考虑增量更新。每次加入一个新的 \(i\),它是当前最大的,那么它就是树根。所以可以考虑树形dp。状态和树的大小,当前深度,\(m\) 层已经有的点个个数。所以记 \(dp_{i,j,k}\) 表示当前子树大小为 \(i\),根在第 \(j\) 层,第 \(m\) 层有 \(k\) 个点的树的个数。由于只有两个儿子,所以直接枚举一边的状态,有
\[dp_{i,j,k}=\sum_{a,b} \binom{i-1}{a} dp_{a,j+1,b}\times dp_{i-1-a,j+1,k-b-[j=m]}
\]
这样就有一个 \(O(n^5)\) 的算法,卡卡常就能过。具体的,对于 \(j>m\) 且 \(k=0\) 的状态,已经和树高没有关系了,方案直接就是 \(i!\)。还有其他剪枝,不再赘述。
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
typedef long long ll;
const int N=1e2+3;
int n,m,K,Mod;
ll C[N][N],dp[N][N][N],fac[N];
ll dfs(int i,int j,int k){
if(i<k||i+1<k*2||k<0) return 0;
if(j>m) return k? 0:fac[i];
if(!i) return !k;
ll &ret=dp[i][j][k];
if(~ret) return ret; else ret=0;
for(int a=0;a<=i-1;a++)
for(int b=0;b<=k;b++)
ret=(ret+C[i-1][a]*dfs(a,j+1,b)%Mod*dfs(i-1-a,j+1,k-b-(j==m))%Mod)%Mod;
return ret;
}
int main(){
n=read(),m=read(),
K=read(),Mod=read();
memset(dp,-1,sizeof(dp));
fac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%Mod;
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mod;
printf("%lld\n",dfs(n,1,K));
}