BZOJ 5215:[Lydsy2017省队十连测]商店购物 组合数+DP

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/88890897

title

BZOJ 5215
在这里插入图片描述
在这里插入图片描述

analysis

  • 30 分做法
    对于 20% 的数据, n 100 w i 100 k 1000 n ≤ 100,wi ≤ 100,k ≤ 1000
    考虑 D P DP ,设 f [ i ] [ j ] f[i][j] 表示考虑了前 i i 家商店,消费总和为 j j
    方案数,暴力枚举每家商店消费了多少钱即可。
    时间复杂度 O ( n w k ) O(nwk)

  • 50 分做法
    对于 50% 的数据, n 100 w i 300 k 100000 n ≤ 100,wi ≤ 300,k ≤ 100000
    考虑优化 DP 转移的复杂度,转移显然可以用 前缀和 优化到
    O ( 1 ) O(1)
    时间复杂度 O ( n k ) O(nk)

  • 70 分做法
    对于 70% 的数据, n , k 5 × 106 m 20 w i 300 n, k ≤ 5 × 106,m ≤ 20,wi ≤ 300
    假如没有上限,那么就是简单的排列组合问题,用隔板法即
    可算出方案数为 C ( k + n 1 , n 1 ) C(k + n − 1, n − 1)
    考虑容斥,暴力枚举哪些商店必然超过了限制,而不关心其
    它商店,那么把 k k 减去这些商店的上限 + 1 +1 之和,即可求出
    贡献。
    时间复杂度 O ( n + k + 2 m ) O(n + k + 2m)

  • 100 分做法
    注意到暴力枚举哪些商店必然超过了限制时,我们事实上只
    关心选的商店的上限之和以及选的商店个数的奇偶性。
    考虑 D P DP ,设 f [ i ] [ j ] f[i][j] 表示考虑了前 i i 个商店,目前选的商店的
    上限 + 1 +1 之和为 j j 时的贡献。
    如果这个商店不选,那么有 f [ i ] [ j ] + = f [ i 1 ] [ j ] f[i][j]+ = f[i − 1][j] ,否则有
    f [ i ] [ j ] = f [ i 1 ] [ j w [ i ] 1 ] f[i][j]− = f[i − 1][j − w[i] − 1]
    a n s = i f [ n ] [ i ] C ( k + n 1 i , n 1 ) ans =∑_{i}f[n][i]*C(k + n − 1 − i, n − 1)
    时间复杂度 O ( n + k + m 2 w ) O(n + k + m^{2}w)

code

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int inf=1e7+10;
const int maxm=310;
const int maxn=1e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
inline int Quick_power(int a,int b)
{
	int ans=1;
	while (b)
	{
		if (b&1)
			ans=1ll*ans*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return ans;
}
int fac[inf],inv[inf];
inline int C(int x,int y)//计算组合数
{
	if (y==-1) return x==-1;
	return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int w[maxm],f[2][maxn];
int main()
{
	int n,m,k;
	read(n);read(m);read(k);
	for (int i=1; i<=m; ++i)
		read(w[i]);
	//统计出前i个商店消费0...j-1的方案数之和
	for (int i=1; i<=maxn-10; ++i)
		f[0][i]=1;
	for (int i=1; i<=m; ++i)
	{
		int o=i&1;
		for (int j=1; j<=maxn-10; ++j)
		{
			f[o][j]=f[o][j-1];
			(f[o][j]+=(f[o^1][j]-f[o^1][max(j-w[i]-1,0)]+mod)%mod)%=mod;
		}
	}
	int o=m&1;
	for (int i=maxn-10; i>=0; --i)
		f[o][i]=(f[o][i]-f[o][i-1])%mod;
	//计算阶乘
	fac[0]=1;
	for (int i=1; i<=k+n; ++i)
		fac[i]=1ll*fac[i-1]*i%mod;
	//计算逆元
	inv[k+n]=Quick_power(fac[k+n],mod-2);
	for (int i=k+n-1; i>=0; --i)
		inv[i]=1ll*inv[i+1]*(i+1)%mod;
	//统计答案
	int ans=0;
	for (int i=1; i<=min(k+1,maxn-10); ++i)
	{
		ans+=1ll*f[o][i]*C(k-i+n-m,n-m-1)%mod;
		ans%=mod;
	}
	if (ans<0) ans+=mod;
	printf("%d\n",ans);
	return 0;
}

summary

考试的时候,想到了组合数,然而只会没有上限的那种。没想到DP。??我也很奇怪,为啥没想到DP。然后不知道怎么回事,评测的时候,全T了。还是知识点掌握的不够熟练。

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/88890897