[DP] ZROI 2017 提高3 T3 建筑

本文介绍了一种使用动态规划解决特定计数问题的方法,该问题涉及在满足一定高度和位置限制条件下,计算可能的排列组合数目。通过定义状态转移方程,实现了对问题的有效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先我们可以简化一下条件,显然只要满足相邻元素满足限制即可。即对于任意的相邻的 i,j 满足 max{hi,hj}>=|xixj|

考虑如果我们已经知道排列的顺序,那么我们很容易统计这种顺序下的答案:

S=n1i=1max{hi,hi+1} ,则相当于选 n 个非负整数,加和不大于 X1S 的方案数。经典的隔板法得 方案数 (X1S+nn)

S 小于 n2 ,现在我们需要对于每个 S 算出有几种方案。可以 DP ,考虑从小到大不断插入,现在插入的是当前的最大值,这样比较好处理。f[i][s][j]j 表示有 j 个间隙待插入,S 表示其他规定不再插入的位置对S的贡献和。这样转移时就考虑插入后新多出来的空,是否规定不再插入。还有往边界插的特殊情况需要考虑。

复杂度O(n4)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=100105;
int n,m,MOD,f[2][10105][115],ans;// f[i][s][j]
LL inv[maxn],fac[maxn],fac_inv[maxn];
LL C(int n,int m){ return fac[n]*fac_inv[m]%MOD*fac_inv[n-m]%MOD; }
int main(){
    scanf("%d%d%d",&n,&m,&MOD); m=m-1+n; // m=X-1+n  C(m-S,n)
    fac[0]=1; for(int i=1;i<=100100;i++) fac[i]=fac[i-1]*i%MOD;
    inv[1]=1; for(int i=2;i<=100100;i++) inv[i]=(LL)(MOD-MOD/i)*inv[MOD%i]%MOD;
    fac_inv[0]=1; for(int i=1;i<=100100;i++) fac_inv[i]=fac_inv[i-1]*inv[i]%MOD;
    f[1][0][0]=1;
    for(int i=1;i<=n-1;i++){
        memset(f[(i&1)^1],0,sizeof(f[(i&1)^1]));
        for(int s=0;s<=i*i&&s<=m-n;s++)
         for(int j=0;j<=i-1;j++) if(f[i&1][s][j]){
            LL f_now=f[i&1][s][j];
            if(j){
                (f[(i&1)^1][s][j+1]+=f_now*j%MOD)%=MOD;
                (f[(i&1)^1][s+(i+1)][j]+=f_now*j*2%MOD)%=MOD;
                (f[(i&1)^1][s+2*(i+1)][j-1]+=f_now*j%MOD)%=MOD; 
            }
            (f[(i&1)^1][s][j+1]+=f_now*2%MOD)%=MOD;
            (f[(i&1)^1][s+(i+1)][j]+=f_now*2%MOD)%=MOD;
         }
    }
    for(int s=0;s<=n*n&&s<=m-n;s++) (ans+=C(m-s,n)*f[n&1][s][0]%MOD)%=MOD;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值