POJ 1742 Coins( 单调队列优化多重背包)

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

题意:

tony有n中硬币想去买一个手表面值分别为A1,A2,An个数分别为C1,C2,Cn。知道手表价格可能的最大值m,但不知道准确值,Tony不喜欢找零。问他的钱能组合出多少种(不超过m)的钱数。

tip:

可行性的多重背包问题,但是正常写法会超时。。所以采用单调队列优化,优化的原理就是当我么的值是1时,转化成为一个01背包,当这个num*v大于总体的m时,很显然是个完全背包,理由是,可以无限的取,直到你背包塞不下为止,剩下的可能,就是多重背包了,

http://blog.csdn.net/flyinghearts/article/details/5898183

讲解多重背包很好的文章QAQ

针对这道题。。。要的不是最大值,而是所有可能性,
bool   dp[I]表示体积为i时候,是否可行(钱数能不能凑出来i)。。。那么如果放入当前这个物品(不知道多少个)之前,v有解的话,v+v[I],v+v[I]*2….直到装不下或者物品数量不够为止,这些体积都可以得到。。。

而这个可能性还是很多的,问题是如何优化一下,使得我们要重复计算的减少,考虑对于所有 d+v d+2v d+3v…都是通过d来转移的,于是可以按照d分类,一口气求出所有的。
对于一组的:d  d+v d+2v d+3v…d+p*v维护k个值(第I种物品有k个),这些里面但凡有一个dp值为true  那么当前这个dp就是true,这个队列维护的值是更新I这个背包之前dp的情况,即从之前别的物品的情况转移,对我们当前这个物品的个数才是合法的。。。而不是改变后,如果是改变后很明显就会出现物品数量不够的情况(转移来的那个体积已经放了几个,现在又放p个肯定超出了)

#include 
#include 
#include 
using namespace std;
int n,m;
const int maxn = 110;
const int maxm = 1e5+10;
int a[maxn],num[maxn],dp[maxm],ds[maxm];//dp[v]:装了v体积得东西了

void init(){
    for(int i = 1; i <= n ; i++){
        scanf("%d",&a[i]);//体积
    }
    for(int i = 1; i <= n ; i++){
        scanf("%d",&num[i]);//数量
    }
    for(int i = 1 ; i <= m ; i++)
        dp[i] = 0;
    dp[0] = 1;
}

void get1(int i){
    for(int l = m ; l >= a[i] ; l--){
        if(dp[l-a[i]]){
            dp[l] = 1;
        }
    }
}

void getfull(int i){
    for(int l = a[i] ; l <= m ; l++){
        if(dp[l-a[i]]){
            dp[l] = 1;
        }
    }
}

void sov(){
    for(int i = 1; i <= n ; i++){
       if(num[i] == 1)  get1(i);
       else if(num[i] * a[i] >= m)  getfull(i);
       else{
           for(int d = 0 ; d < a[i] ; d++){
                int sum = 0,st = 0,en = -1;
                for(int v = d,cnt = 0; v <= m ; v += a[i],cnt++){
                    //printf("num[%d] = %d  cnt = %d\n",i,num[i],cnt);
                    if(cnt == num[i]+1){
                        sum -= ds[st++];
                        //printf("vvvv = %d\n",v-cnt*a[i]);
                        cnt--;
                    }
                   // printf("sum[%d] = %d\n",v,sum);
                    sum += dp[v];
                    ds[++en] = dp[v];
                   // printf("dp[%d] = %d\n",v,dp[v]);
                    if(sum){
                      dp[v] = 1;
                     // printf("v = %d\n",v);
                    }
                }
           }
       }
    }
    int cnt = 0;
    for(int i = 1; i <= m ; i++)
        cnt += dp[i];
    printf("%d\n",cnt);
}

int main(){
    while(~scanf("%d%d",&n,&m) && n){
        if(m == 0){
            printf("0\n");
            continue;
        }
        init();
        sov();
    }

}

你可能感兴趣的:(acm,基本算法)