传送门

 

参考资料:

  [1]:挑战程序设计竞赛(第二版) P62 多重部分和问题

  [2]:http://www.hankcs.com/program/cpp/poj-1742-coins.html

题解:

  具体解析看以上参考资料即可,下面只是谈谈我对这道题的进一步理解。

  1.dp[ i ][ j ] 定义不同,码出程序的时间复杂度也不同

    ①若定义bool dp[ i ][ j ]的含义为 :前 i 种硬币是否可以凑成 j 元 。

     为了用前 i 中硬币凑成 j 元,也就需要前 i-1 中硬币凑成 j , j-a, j-2×a,...., j-ci×ai 中的某一种;

     由此,可得状态转移方程为dp[ i ][ j ]={ 0≤k≤ci,k×a≤ j 时,判断是否∃k,使得dp[i-1][j-k×ai]为真};

for i : 1 to n
    for j : 0 to m
        for k : 0 to c[i] && k*a[i] ≤ j
            //dp[i][j]为true⇔dp[i-1][j-k*a[i]]有一个为true即可
            dp[i][j]=dp[i][j] | dp[i-1][j-k*a[i]];

     易得此状态转移的时间复杂度为O(n*m*c[i]),而题干 m 的最大取值为 1e5,此方法指定超时。

    ②若定义int dp[ i ][ j ]的含义为:用前 i 种硬币凑成 j 元,第 i 种硬币剩余的个数,不能得到的情况下赋值为-1。

     根据此定义,可得状态转移方程为:

     poj 1742 Coins(多重背包)

     分析此状态的时间复杂度为O(n*m) ~ O(m) ( n << m)

     但,提交会返回MLE................

     那这只需要优化一下空间就好了,当然选择滚动数组啦。

       注意:用滚动数组时,第二层循环从0开始;

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 #define memF(a,b,n) for(int i=0;i <= n;a[i++]=b);
 6 const int maxn=1e5+50;
 7 
 8 int n,m;
 9 int a[maxn];
10 int c[maxn];
11 int dp[maxn];
12 
13 int Solve()
14 {
15     memF(dp,-1,m);
16     dp[0]=0;
17     for(int i=1;i <= n;++i)
18     {
19         for(int j=0;j <= m;++j)
20             if(dp[j] >= 0)///如果在这之前就可以凑出j元,则不需要使用当前硬币
21                 dp[j]=c[i];
22             ///如果当前面值a[i]比所要凑的面值j大
23             ///或在配更小的数[j-a[i]]时就用光了c[i],则赋值为-1
24             else if(j < a[i] || dp[j-a[i]] <= 0)
25                 dp[j]=-1;///来到此判断语句的隐藏条件为在这之前并没有凑出j元
26             else
27                 dp[j]=dp[j-a[i]]-1;
28     }
29     return count_if(dp+1,dp+m+1,bind2nd(greater_equal<int>(),0));///总额0不算在答案内
30 }
31 int main()
32 {
33     while(~scanf("%d%d",&n,&m) && n+m)
34     {
35         for(int i=1;i <= n;++i)
36             scanf("%d",a+i);
37         for(int i=1;i <= n;++i)
38             scanf("%d",c+i);
39         printf("%d\n",Solve());
40     }
41     return 0;
42 }
View Code

相关文章: