DP
来源:互联网 发布:java基础电子书 编辑:程序博客网 时间:2024/06/06 01:24
拿到这道题,第一反应应该是01背包求方案数,但是并不是这么简单。经分析发现,如果我们选择“最终无法选取的最小物品”作为加法原理的分割方式,将不会有重复或者遗漏的状态。同时我们发现,如果我们最终无法选取的最小物品的价值为x,那么我们肯定是选择了价值小于x的所有物品了的。
设最终无法选取的最小物品为i,那么无法选取的最小物品为i的方案总数就是 选了所有价值为val[i]的物品并且用剩下的钱(剩下的钱肯定不足以购买i)买其它物品的方案数。选择全部价值小于x的物品只有1个方案,那就是全选= =。所以根据乘法原理,求上面说的那个方案数就等价于求 花费[小于i的物品的价值总和, 小于i的物品的价值总和 + i的价值)这么多钱买价值大于i的物品的方案数。这个可以用01背包来求。
但是有两点需要注意:
1.由于题目要求两种选择方案不同当且仅当选择的物品的集合不同,因此我们可以把价值相同的物品看成“有那么一点点的差异”,即不进行特殊处理。
2.如果连一个物品都选不中,是不是就没有方案呢?最好是必须加一个特判,当没有方案数时将方案数置为1.
参考代码
#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>#include <bitset>using std::cin;using std::cout;using std::endl;typedef int INT;inline INT readIn(){ INT a; scanf("%d", &a); return a;}const INT mod = 1e7 + 7;const INT maxn = 1005;INT n, m;INT num[maxn];INT sum[maxn];INT f[maxn][maxn];void run(){ n = readIn(); m = readIn(); for(int i = 1; i <= n; i++) { num[i] = readIn(); } std::sort(num + 1, num + 1 + n, std::greater<INT>()); for(int i = 1; i <= n; i++) { sum[i] = sum[i - 1] + num[i]; } INT ans = -1; f[0][0] = 1; for(int i = 1; i <= n; i++) { //不选的最小价值为num[i] //事实上这里没有处理num[i]是最小值,也就是没有方案的情况 for(int j = std::max(0, m - (sum[n] - sum[i - 1]) + 1); //到刚刚不能选num[i] j <= m - (sum[n] - sum[i]); //从把所有小于num[i]的选了 j++) { ans = (ans + f[i - 1][j]) % mod; } for(int j = m; j >= 0; j--) { //01背包方案总数 f[i][j] = f[i - 1][j]; //不选 f[i][j] %= mod; if(j - num[i] >= 0) //选 { f[i][j] += f[i - 1][j - num[i]]; f[i][j] %= mod; } } } if(ans == -1) ans = 1; //特判:一个都不能选还是有一种方案 else ans = (ans + 1) % mod; cout << ans << endl;}int main(){ run(); return 0;}