HDU 2844Coins(涉及包含三种背包问题)

时间:2021-04-28 18:43:40

题意:

给你n种面值分别为A1,A2,A3………..An的硬币,每种硬币对应的数量分别是C1,C2,C3………Cn。问:在可以凑出多少种不同的面值,凑出来的面值必须在1~m之间。

解题思路:

乍一看以为是母函数问题,但的确可以使用母函数(在不顾时间的情况下也可以得出正确答案),由于题目所给的数值太大,m <= 100000,而硬币数量Ci <= 1000。这套用母函数简直就被炸飞了。最终还得老老实实用动态规划。
乍二看,这不是多重背包问题吗,直接三个for循环干上去,很悲剧,肯定超时,数据这么大,两个for循环都有可能崩,更别说三个了。
最终还是弱弱的参考了大神们的代码,发现这道题虽然套着多重背包的羊皮,但其实却需用到01背包与完全背包的概念才能在不超时的情况下跑过。
这里安利一个讲述完全背包的博客:http://blog.csdn.net/insistgogo/article/details/11081025
感觉讲的超级棒!!!

真正的解题思路:

首先需要用到的第一个技巧便是多重背包问题的二进制优化,使其转变成01背包问题,减少一重循环(如果不明白二进制优化的可以看看我的背包问题2)

第二个技巧:当Ai * Ci >= m时,就没必要再使用二进制优化了,此时可以直接把这种面值的硬币当成无限多个,直接上完全背包的模板。如果优化成二进制比不优化还慢。
第三个技巧:采用滚动数组,由于n*m的最大值为10000000,此处博主怂,不敢开二维怕崩,所以就采用一维。。。。不知有没有壮士开二维过了。
完全背包的状态转移方程:
d[i] = max(d[i],d[i-an]+an); //an代表硬币面值,i代表凑得总钱数。
注意这里的循环是从,i = an 到 i = sum。(why?见上面链接)

01背包的状态转移方程:
d[i] = max(d[i],d[i-an]+an);

具体代码:

#include <set>
#include <numeric>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cctype>
#include <string>
#include <sstream>
#include <map>
#include <functional>

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
#define REP(idx1,num1) for(int idx1=0;idx1<(num1);idx1++)
#define pb push_back
#define empb emplace_back
#define mp make_pair
#define mem(s) memset(s,0,sizeof(s));
const double EPS = 1e-6;
const int maxn = 100000 + 10;
int n,m;
int a[110],c[110];
int d[maxn];
int sum = 0;
//完全背包
//an对应着硬币面值,cnt对应着此面值的硬币个数
void completebag(int an){
    for(int i = an; i <= sum; ++i)
        d[i] = max(d[i],d[i-an]+an);
}
//01背包
void zeroonebag(int an){
    for(int i = sum; i >= an; --i)
        d[i] = max(d[i],d[i-an]+an);
}
//二进制优化
void change(int an,int cnt){
    if(an*cnt >= m){
        completebag(an);
        return;
    }
    else{
        int tmp = 0;
        for(int j = 1; ; j *= 2){
            if(cnt > j){
                tmp = an * j;
                cnt -= j;
                zeroonebag(tmp);
            }
            else{
                tmp = an * cnt;
                zeroonebag(tmp);
                break;
            }
        }
    }
}
int main(){

    while(scanf("%d%d",&n,&m) && n){
        for(int i = 1; i <= n; ++i){
            scanf("%d",&a[i]);
        }
        for(int i = 1; i <= n; ++i){
            scanf("%d",&c[i]);
            sum += a[i] * c[i];
        }
        mem(d);
        if(sum > m) sum = m;
        for(int i = 1; i <= n; ++i){
            change(a[i],c[i]);
        }
        int ans = 0;
        int kase[maxn];

        //判断出现了多少种面值
        mem(kase);
        kase[0] = 1;
        for(int i = 1; i <= m; ++i){
            if(!kase[d[i]]){
                ans++;kase[d[i]] = 1;
                //cout <<d[i] <<" ";
            }
        }
        //cout << endl;
        printf("%d\n",ans);
    }
    return 0;
}