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} inom{i-1}{a} dp_{a,j+1,b} imes 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
",dfs(n,1,K));
}
原文地址:https://www.cnblogs.com/wwlwQWQ/p/15380531.html