Crash的游戏 [组合+递推]
题面
思路
问题转化
这个问题的核心在于,我们需要把“加入一个球、拿出一个球”这两个操作转化一下
因为显然两个操作同时进行的话,我们没有办法从单纯的组合意义去分析
我们首先把$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$,分别表示选出$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);
}
}