背包方案数问题(礼物)

给个题面——hdu2126

数字组合-背包方案数问题

简述:给你n块钱,有m种钱币,每种钱币只能用一次,问组成n块钱有多少种方法。

给个数据(结果对1e7+7取模):

Input

6 25

8 9 8 7 16 5

Output

15

Input

30 250

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

output

6509431

Analysis

这是一道想法很清奇的dp题。思维难度和代码难度都较大。

既然你要考虑用dp做,那你肯定会毫无疑问地去选择前(i)个物品,选(j)个和钱数(k)作为状态,用背包来计算方案数。

也就是

[dp[i][j][k] += dp[i-1][j][k] ]

[if(k >= v[i]) dp[i][j][k] += dp[i-1][j-1][k-v[i]] ]

事实上这样会超时。

所以要怎么做?

有大佬用更好的方法,思路诡异。

先将物品排升序,枚举状态(i),表示第(i)个物品是未被选的物品中价值最小的一个物品。

那么价值比它小的物品,都在它左面,而且都会被选,把他们用前缀和维护起来。

现在你剩余一些钱(j),从后面选取一些物品使价值最大但不超过(j),这就是背包。

所以一共二维,时间复杂度空间复杂度皆降低,完美。

咳咳,这是人能想出来的吗……

Code

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<cstring>
#define ll long long
const int MAXN=1010;
const int mod=1000000007;
using namespace std;
ll dp[MAXN][MAXN],n,m,minn,w[MAXN];
ll sum[MAXN];
int cmp(int a,int b){ return a>b; }

int main(){
    scanf("%lld%lld",&n,&m);
    for ( int i=1;i<=n;++i) scanf("%lld",&w[i]); dp[0][0]=1;
  sort(w+1,w+n+1,cmp);
    for (int i=n;i;--i) sum[i]=sum[i+1]+w[i];
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(int j=m-sum[i+1];j>=0&&j>m-sum[i+1]-w[i];j--)
            ans+=dp[i-1][j],ans%=mod;
        for(int j=m;j>=0;j--){
            dp[i][j]=dp[i-1][j];
            if(j-w[i]>=0) dp[i][j]+=dp[i-1][j-w[i]],dp[i][j]%=mod;
        }
    }
    if(sum[1]<=m) ans++,ans%=mod;
    printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/Ch-someone/p/9607682.html