CF1580B Mathematics Curriculum

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));
}
posted @ 2021-10-08 16:37  Kreap  阅读(72)  评论(0编辑  收藏  举报