【AcWing蓝桥杯】1234倍数问题(背包dp+优化)

题意:现在有n个数,从这 n个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。
数据范围:1≤ n ≤1e5   1≤ K ≤1e3  给定的n个数均不超过 1e8
样例:
4 3
1 2 3 4
输出:
9
数据范围是10w,非常大,但是这是蓝桥杯的题,可以直接暴力枚举拿一部分的分值。
方法1:暴力枚举,虽然会超时,但可以过一些范围小的样例
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int arr[N];
int n, m;
int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    int ma = -1e9;//这个点有些题会踩坑,尽量初始化成一个非常小的数
    for (int i = 0; i < n - 2; i++) {
        for (int j = i + 1; j < n - 1; j++) {
            for (int k = j + 1; k < n; k++) {
                int v = arr[i] + arr[j] + arr[k];
                if (v % m == 0) {
                    ma = max(ma, v);
                }
            }
        }
    }
    cout << ma << endl;
    return 0;
}
方法2:背包DP(给你一些数,从中挑出一些数满足某些条件,每个数只能选0次或者1次,多一个限制条件就多一维空间)

首先考虑右边的集合,不选第i个元素,意思就是从前i-1中选,其它俩维不变,也就是f[i-1][j][k];

再看左边的集合,选第i个元素:

这里的每一行都是一种方案,每种方案都含有第i个元素,要求在这些方案中的最大值,因为每种方案都含有第i个元素,也就是我们只要求出左边1~i-1元素里面的最大值,我们把a[i]先去掉,最后把a[i]就行了,



问题接着就转化求左边的最大值了,这个集合表示从前i-1个数中选,且选j-1个数(已经选了a[i],只剩下j-1个数),且模数为k-a[i](这个下面有讲解),即为f[i-1][j-1][(k-a[i])%m];

模数(k-a[i])%m的原因:(左边的+右边的)%m==k

右边的是a[i],所以左边的==(k-a[i])%m, 因为给的k的范围比a[i]小,所以可能会为负数,而负数取模仍为负数,我们可以这样处理: (k-a[i]%m+m)%m



综上,我们要的状态计算就得到了f[i][j][k]=max(f[i-1][j][k],f[i-1][j-1][(k-a[i])%m)]);

看式子可以发现第i层的结果只用到了第i-1层,所以我们优化成俩层:f[j][k]=max(f[j][k],f[j-1][(k-a[i])%m)]),只需把j按从大到小枚举。这里为啥不是从小到大枚举:当枚举到第i层时,如果j从小到大枚举,那f[j]又是在f[j-1]只后遍历,此时f[j-1]已经存的是第i的值,所以得到的答案是f[i][j-1],而我们想要的是f[i-1][j-1],大家也可以按照下面的图推一下那3个空格的值

要10点了,顶不住了,吃个晚饭再来。。😏😏😏






干了一碗8块的粉还看了半局KPL灰常nice,继续哈😮😮😮

emmm,此题还没有结束,虽然空间是优化了,但这样时间复杂度是N*3*K 也就是 3*10e8😅😅😅

还是会有超时的风险。

题目中有求最大值这就有个巧妙的贪心,比如3,7,11,15,19这5个数对4取模得到的结果都是3,我们可以只要存取模相同最大的三个数11,15,19就行了,因为有第五个数19存在,答案中肯定不会包含第一个数3。这样加上一个贪心,本来要枚举1e5个数就优化成只要枚举K*3也就是3000个数

至此,此个《爽题》就大致over

😉😉😉😉

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e3 + 10;
int f[4][N];
vector<int>a[N];
int n, k;
int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        a[x % k].push_back(x);
    }
    memset(f, -0x3f, sizeof(f));
    f[0][0] = 0;
    for (int i = 0; i < k; i++) {
        sort(a[i].begin(), a[i].end());
        reverse(a[i].begin(), a[i].end());
        for (int u = 0; u < 3 && a[i].size(); u++) {
            int x = a[i][u];
            for (int j = 3; j >= 1; j--) {
                for (int m = 0; m < k; m++) {
                    f[j][m] = max(f[j][m], f[j - 1][(m - x % k + k) % k] + x);
                }
            }
        }
    }
    cout << f[3][0] << endl;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值