Crash的游戏 [组合+递推]

题面

1226867-20180929153701524-1235410087.png

思路

问题转化

这个问题的核心在于,我们需要把“加入一个球、拿出一个球”这两个操作转化一下

因为显然两个操作同时进行的话,我们没有办法从单纯的组合意义去分析

我们首先把$m$个球拿出来,表示全部都选拿走球

然后对于我们选定的加入球的操作,我们一次性加入两个球

这样问题就变成了一个单纯加入球的问题了

左右分开

现在的问题是这样的:

给定$n-m$个球,你有$m$次机会,每次可以加入两个球,最后你会拿出$k$个球,问总方案数

我们把$k$个球的来源分开考虑

假设$k$个里面有$i$个来自于原来的$n-m$个球,$k-i$个来自于新加入的球

那么选出$i$个的方案数应该为$C^i_{n-m}$

后面新加入的球,我们考虑一个递推:$f[i][j]$表示从$i$对球中拿了东西,一共取出来了$j$个

那么新加入一对球,可以选择拿一个或者拿两个,因此可以写出方程

$g[i][j]=f[i-1][j-2]+f[i-1][j-1]*2$

计算答案

这个方程得到之后就好办了

我们先枚举$k$个里面从原来球中选出的个数,再枚举剩下的$k-i$用了多少对球($j$)

然后除了上面的两个东西要乘起来之外,还要再乘以$C^{m}_j$和$2^{m-j}$,分别表示选出$j$对的方案,以及剩下的没有取出的东西做出的贡献

式子如下:

$Ans = \sum_{i=0}^k \sum_{j= \frac{k-i}{2} }^{k-i } C(n-m,i) \ast C(m,j) \ast 2^{m-j} \ast f[k-i][j]$

这里面的组合数每次询问单独处理,$f$数组可以预处理好,总复杂度$O(Tk^2)$

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define MOD 1000000007
using namespace std;
ll n,m,k,f[1010][1010],suf[510],suff[510],pre[510],C[510],CC[510],pw[510];
ll qpow(ll a,ll b){
    ll re=1;
    if(b<0) return 0;
    while(b){
        if(b&1) re=re*a%MOD;
        a=a*a%MOD;b>>=1;
    }
    return re;
}
void init(ll p,ll q){
    memset(C,0,sizeof(C));memset(CC,0,sizeof(CC));
    int i;C[0]=1;CC[0]=1;
    suf[1]=p;suff[1]=q;

    for(i=2;i<=min(p,k);i++) suf[i]=suf[i-1]*(p-i+1ll)%MOD;
    for(i=2;i<=min(q,k);i++) suff[i]=suff[i-1]*(q-i+1ll)%MOD;

    for(i=1;i<=min(p,k);i++) C[i]=suf[i]*pre[i]%MOD;
    for(i=1;i<=min(q,k);i++) CC[i]=suff[i]*pre[i]%MOD;
}
void getf(){
    int i,j,len=310;
    f[0][0]=1;
    for(i=1;i<=len;i++){
        for(j=1;j<=i*2;j++){
            f[i][j]=(f[i-1][j-1]*2+f[i-1][j-2])%MOD;
        }
    }
}
int main(){
    getf();int T;scanf("%d",&T);
    pre[1]=1;
    for(int i=2;i<=500;i++) pre[i]=(pre[i-1]*qpow(i,MOD-2))%MOD;
    while(T--){
        scanf("%lld%lld%lld",&n,&m,&k);
        init(n-m,m);
        ll ans=0,tmp;int i,j;
        for(i=0;i<=k;i++) pw[i]=qpow(2,m-i);
        for(i=0;i<=k;i++){
            tmp=0;
            for(j=(k-i+1)/2;j<=k-i;j++){
                (tmp+=CC[j]*pw[j]%MOD*f[j][k-i]%MOD)%=MOD;
            }
            (ans+=tmp*C[i]%MOD)%=MOD;
        }
        printf("%lld\n",ans);
    }
}

转载于:https://www.cnblogs.com/dedicatus545/p/9724121.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值