AcWing 1047 糖果(01背包问题应用)

[题目概述]

由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。
在这一天,Dzx可以从糖果公司的 N 件产品中任意选择若干件带回家享用。
糖果公司的 N 件产品每件都包含数量不同的糖果。
Dzx希望他选择的产品包含的糖果总数是 K 的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。
当然,在满足这一条件的基础上,糖果总数越多越好。
Dzx最多能带走多少糖果呢?
注意:Dzx只能将糖果公司的产品整件带走。

输入格式

第一行包含两个整数 N 和 K。
以下 N 行每行 1 个整数,表示糖果公司该件产品中包含的糖果数目,不超过 1000000。

输出格式

符合要求的最多能达到的糖果总数,如果不能达到 K 的倍数这一要求,输出 0。

数据范围

1 ≤ N ≤ 100,
1 ≤ K ≤ 100,

输入样例:

5 7
1
2
3
4
5

输出样例:

14

样例解释
Dzx的选择是2+3+4+5=14,这样糖果总数是7的倍数,并且是总数最多的选择。

题意就是让我们从n个物品中选任意个使得权值和最大,并且是k的倍数,01背包就是解决这种选择问题

状态表示:f[i][j]
********集合:所有只考虑前i个数,切总和对k的余数为j的所有方案
********属性:最大值
状态计算:分为两个部分,不选第i个数,选第i个数
********不选:f[i -1][j]
********选:f[i - 1][(j - a[i]) % k + a[i]
这个题和波动数列的证明过程是相同的,都是同余定理,此处就不做详细解释,不同的细节之处在代码中解释。

  • 完整代码
#include  
#include 
#include 
#include 

using namespace std;

const int N = 110;

int a[N], f[N][N], n, k; 

int main(){
    cin >> n >> k;
    for(int i = 1; i <= n ; i ++){
        cin >> a[i];
    } 
    
    // 将所有数初始化为无穷小(-1e6以下),这两种方法都可以,但是f[0][0]要初始化为1
    // 因为f[0][1], f[0][2]等都没有意义,不选数它的余数不可能大于0
    memset(f, -0x3f, sizeof f);
    // for(int i = 0; i < N; i ++)
    //     for(int j = 0; j < N; j ++)
    //         f[i][j] = -1e8;
            
    f[0][0] = 0;
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j < k; j ++){
        	// 第二种()中的内容看起来很复杂,(j + k - a[i] % k)这个+k再%k
        	// 是为了使()中的内容非负,其他就是和上面分析的一样了
            f[i][j] = max(f[i - 1][j], f[i - 1][(j + k - a[i] % k) % k] + a[i]);
        }
    }
    // 我们要求的是k的倍数,所以余数是0
    cout << f[n][0];
    return 0;
}

本题的分享就结束了, 也是01背包问题的应用,就是代码的细节不容易想,有问题的小伙伴可以留言
别忘了点赞关注加收藏!

你可能感兴趣的:(算法,DP,01背包问题)