2019-3-15 模拟赛 T1
题意
给你一个数 \(k\) ,\(n\) 个桶,有 \(m\) 个桶有容量上限 \(w_i\) ,其它桶则没有。求把数拆开放到各个桶里,最终得到序列的方案数。
数据范围 : \(n,k<=5*10^6\) , \(m,w_i<=300\) 。
做法
考虑到 \(m\) , \(w[i]\)很小,我们先假装不存在有限制的桶。
等等!这样不就是插板法裸题了吗?答案显然是\(C^{n-1}_{k+n-1}\)的呀。
我们再来考虑有限制的情况,因为数据很小,我们考虑把 \(k\) 分为两份,一份直接暴力dp算出方案数,一份用刚刚的公式计算。枚举分割点(它不会超过 \(90000\) 个),直接相加即可。
最后还要注意用前缀和优化dp转移,组合数预处理等。
做完了,复杂度 \(O(n+m^2*w_{max})\) 。
代码实现
#include<cstdio>
const long long mod=1e9+7;
long long pu[10000005],inv[10000005];
long long w[305],dp[305][90005],sum[305][90005];
long long qpow(long long val,long long k){
long long ret=1;
while(k){
if(k&1) ret=ret*val%mod;
val=val*val%mod; k>>=1;
}
return ret;
}
long long C(long long n,long long m){
return (pu[n]*inv[m]%mod)*inv[n-m]%mod;
}
void init(int maxn){
pu[0]=1;
//!!!!
for(int i=1;i<=maxn;i++) pu[i]=pu[i-1]*i%mod;
inv[maxn]=qpow(pu[maxn],mod-2);
for(int i=maxn-1;i>=0;i--) inv[i]=inv[i+1]*(i+1ll)%mod;
dp[0][0]=1;
return ;
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
init(n+k);
for(int i=1;i<=m;i++) scanf("%lld",&w[i]);
int sumw=0;
for(int i=1;i<=m;i++){
sum[i-1][0]=dp[i-1][0];
for(int j=1;j<=sumw;j++)
sum[i-1][j]=(sum[i-1][j-1]+dp[i-1][j])%mod;
for(int j=sumw+1;j<=sumw+w[i];j++)
sum[i-1][j]=sum[i-1][j-1];
sumw+=w[i];
dp[i][0]=1;
for(int j=1;j<=sumw;j++){
if(j<=w[i]) dp[i][j]=(dp[i-1][j]+sum[i-1][j-1])%mod;
else dp[i][j]=(dp[i-1][j]+(sum[i-1][j-1]-sum[i-1][j-w[i]-1]+mod)%mod)%mod;
}
}
if(n==m){
if(k>=90004)
return 0*puts("0");
return 0*printf("%lld\n",dp[n][k]);
}
long long ans=0;
for(int i=0;i<=sumw;i++){
if(dp[m][i]==0) continue;
ans=(ans+dp[m][i]*C(k-i+(n-m)-1,(n-m)-1)%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}