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;}
原创粉丝点击