[NOIp模拟赛][01背包] 礼物

题面

\(T\) 要给他的妹妹买礼物。他会不断的买,直到自己身上没有足够的钱来买任何一件礼物为止,他想知道有多少种方案符合他买礼物的方式。

我们认为两种选择方案不同当且仅当它们选取的的物品的集合不同。

因为答案可能很大,你只需要输出答案对 \(10^7+7\) 取模的结果即可。

共有 \(n\) 件礼物,价格为 \(C_i\),小 \(T\) 持有的钱数为 \(m\)
对于 \(100%\) 的数据,满足 \(1 \le n \le 1000\)\(1 \le m \le 1000\)\(0 \le C_i \le m\)


这样的统计方案题,如果抛开“必须买到没有钱”这个限制,是可以用 \(01\) 背包统计方案的:
\(f_i = \sum f_{i-C_j},\ j \in \{x | i - x \ge 0\}\)

沿着这个思路想下去,我们可以考虑仍然用这种方式统计方案,然后将对答案有贡献的方案计入答案。

我们可枚举现在没有选择的价值最小的物品 \(i\),再枚举一个剩余的钱数 \(j\),满足 \(j \lt C_i\),这样的答案便是合法的。

对于 \(i\) 的枚举,直接排序一遍便可以顺序扫描了。

代码:

# include <iostream>
# include <cstdio>
# include <algorithm>
# define MAXN 1005
# define MAXC 1005

const int MOD = 1e7+7;
int co[MAXN], f[MAXC];

int main(){
	int n, m;

	scanf("%d%d", &n, &m);

	int sum = 0;
	for(int i = 1; i <= n; i++){
		scanf("%d", &co[i]);
		sum += co[i];
	}

	std::sort(co+1, co+n+1);

	int ans = 0; f[0] = 1;
	if(m >= sum){ // 特判
		ans = 1;
	}
	else{
		for(int i = n; i >= 1; i--){
			sum -= co[i];

			for(int j = std::max(0, (m-sum)-(co[i]-1)); j <= m-sum; j++){
				ans = (ans += f[j]) % MOD;
			}
			for(int j = m; j >= co[i]; j--){
				f[j] = (f[j] + f[j-co[i]]) % MOD;
			}
		}
	}

	printf("%d", ans);

	return 0;
}

复杂度:\(O(nm)\)

posted @ 2020-09-23 17:14  ChPu437  阅读(226)  评论(0编辑  收藏  举报