POJ-1742-Coins-DP-男人八题

题目来源:http://poj.org/problem?id=1742
题目内容:Coins
Time Limit: 3000MS Memory Limit: 30000K
Total Submissions: 39405 Accepted: 13359
Description

People in Silverland use coins.They have coins of value A1,A2,A3…An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn’t know the exact price of the watch.
You are to write a program which reads n,m,A1,A2,A3…An and C1,C2,C3…Cn corresponding to the number of Tony’s coins of value A1,A2,A3…An then calculate how many prices(form 1 to m) Tony can pay use these coins.
Input

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3…An,C1,C2,C3…Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.
Output

For each test case output the answer on a single line.
Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
Sample Output

8
4
Source

LouTiancheng@POJ

题目分析:如果审题不谨慎就会把题目看成从所给种数的硬币中凑出一个接近要求的价格,但是仔细看一看再结合样例2,题目要问的是在所给出的n种硬币当中,凑成价格小于m的方法有多少种,比如,价格是10,那么从1-10共有10种价格,只要所给的硬币能够凑成其中一种,那么方法数就加1。
简单来说,第一次看到这种题目的时候,如果把它当成简单的背包问题来做,那么动态转移方程其实是很容易写出来的,定义dp[i][j]为前i种硬币能否凑成j,那么这个dp会是一个bool类型的数组,转移方程如下:dp[i][j] |= dp[i-1][j-k*A[i]]。(0<=k<=C[i] && j-k*A[i]>=0)很明显,只要开三个循环就可以得出结论。主要的代码如下:

void solve(){
    for(int i = 1 ; i <= n; i ++){
        for(int j = 1 ; j <= m; j ++){
            for(int k = 0 ;k <= C[i] && j-k*A[i] >=0;k++){
                dp[i][j] |= dp[i][j-k*A[i]];
            }
        }
    }
}

这种代码在一般DP里面来说理解起来是比较容易的,但是弊端也非常明显,三重循环,不用提交都知道一定会TLE,原因很大一部分在于这样用DP数组存储的信息太少了,在这里dp[i][j]是bool类型的,只有0,1两种结果,这对于二维DP来说提供的信息是不足的,因此我们需要优化。
优化的地方并不是剪枝什么的,而是要改变整个dp的定义,为了记录更多的信息,我们完全可以定义dp[i+1][j]为前i种硬币凑成j后,第i种硬币剩余的个数,如果不能凑成j,则dp的值为-1。
那么这时候我们的动态转移方程就变成了:
dp[i+1][j] = C[i]…………………………这是前i-1种硬币已经可以凑成j的情况,那么第i种硬币就不用花费。
dp[i+1][j] = -1……………………………这是当第i种硬币已经用完或者说用了第i种也确定无法凑成j的情况
dp[i+1][j] = dp[i+1][j-A[i] -1…………………….除了上面两种情况以外的其他所有情况
转化成代码:

for(int i = 1 ; i <= n ; i ++){
    for(int j = 1; j <= m ; j ++){
        if(dp[i][j] >=0){
            dp[i+1][j] = C[i];
        }else if(A[i] > j || dp[i+1][j-A[i]]<=0){
        //1.当前硬币的价值大于j,则不可能通过当前硬币凑成j
        //2.当前硬币已经用完了
            dp[i+1][j] = -1;
        }else{
            dp[i+1][j] = dp[i+1][j-A[i]] -1;
        }
    }
}

这个时候时间复杂度已经为n*m了,已经可以AC掉POJ上的了,在时间复杂度上,小弟不才,已经没有改进的余地了,但是在空间复杂度上,利用滚动数组可以将二维dp降维到一维dp,能够实现降维的主要一点原因是每一次当我们处理完dp[i][1…….m]的时候,这一层dp的唯一一个作用就是给dp[i+1][1……..m]提供上一步的状态,所以完全可以将dp[i+1][1……..m]覆盖掉dp[i][1…….m],因为以后不可能会再用到dp[i][1…….m]了,修改后的最终代码是:

for(int i = 1 ; i <= n ; i ++){
    for(int j = 1 ; j <= m ; j ++){
        if(dp[j] >= 0){
            dp[j] = C[i];
        }else if(A[i] > j || dp[j-A[i]]<=0){
            dp[j] = -1;
        }else{
            dp[j] = dp[j - A[i]]-1;
        }
    }
}

以上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值