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;
}

posted @ 2019-03-15 22:14  臼邦庶民  阅读(95)  评论(0编辑  收藏  举报