bzoj4498: 魔法的碰撞(dp+组合数学)

本文探讨了一道题目——bzoj4498:魔法的碰撞,通过动态规划(dp)和组合数学的方法解决区间重叠问题。首先,设想区间不相交时的简单组合计数,接着分析相邻区间如何导致重叠并转化为额外的多余格子。将魔法师按攻击范围降序排列,然后用dp状态转移方程统计每个额外格子的排列数。文章附带了解决问题的代码实现。
摘要由CSDN通过智能技术生成

传送门
思路:
考虑理想情况,所有魔法师的区间都不相交,那么总长度减去所有区间长度即为多余格子数,可以直接组合计数了。
然而,事实上,对于2个相邻的区间,不妨假设左边的区间更小,那么两个区间之间就有【左边区间的右半部分】的格子可以重叠。因此,相应的长度就可以作为额外的多余格子。
于是问题转化为统计每个多余格子数目分别对应了多少种排列。
考虑DP。
由于额外的多余的格子取决于两个相邻区间的大小,所以我们把魔法师按攻击范围从大到小排序,然后逐个插入,把这些还会插入更小魔法师的位置称为空位。
设:dp[i][j][s]表示插入了前i个魔法师、目前有j个空位、提供了s个额外的格子。
然后 d p dp dp即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
typedef long long ll;
const int mod=1e9+7,inv2=(mod+1)>>1;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline void Add(int&a,const int&b){a=a+b>=mod?a+b-mod:a+b;}
inline void Dec(int&a,const int&b){a=a>=b?a-b:a-b+mod;}
inline void Mul(int&a,const int&b){a=(ll)a*b%mod;}
const int N=45,M=1e6+5;
int n,l,d[N],f[N][N][N*N<<2],s=0;
int fac[M<<1],ifac[M<<1];
inline void init(int up){
    fac[0]=fac[1]=ifac[0]=ifac[1]=1;
    for(ri i=2;i<=up;++i)fac[i]=mul(i,fac[i-1]),ifac[i]=mul(ifac[mod-mod/i*i],mod-mod/i);
    for(ri i=2;i<=up;++i)Mul(ifac[i],ifac[i-1]);
}
inline int C(int n,int m){return n<m?0:(ll)fac[n]*ifac[m]%mod*ifac[n-m]%mod;}
int main(){
    scanf("%d%d",&l,&n);
    init(l<<1);
    for(ri i=1;i<=n;++i)scanf("%d",&d[i]),--d[i];
    sort(d+1,d+n+1),reverse(d+1,d+n+1);
    f[0][1][0]=1;
    for(ri i=1;i<=n;++i){
        s+=d[i];
        for(ri j=0;j<=i+1;++j){
            for(ri k=0;k<=s*2;++k){
                if(j&&k>=2*d[i])Add(f[i][j][k],mul(f[i-1][j-1][k-2*d[i]],j-1));
                if(k>=d[i])Add(f[i][j][k],mul(f[i-1][j][k-d[i]],j<<1));
                Add(f[i][j][k],mul(f[i-1][j+1][k],j+1));
            }
        }
    }
    int ans=0;
    for(ri i=0;i<=2*s;++i){
        if(!f[n][0][i])continue;
        Add(ans,mul(f[n][0][i],C(l-i,n)));
    }
    cout<<ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值