欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

疑难杂题笔记——倍数问题

程序员文章站 2023-12-27 08:46:27
...

学习来源:https://www.acwing.com/video/795/

题目

众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。

但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。

现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。

数据保证一定有解。

输入格式

第一行包括 2 个正整数 n, K。

第二行 n 个正整数,代表给定的 n 个数。

输出格式

输出一行一个整数代表所求的和。

数据范围

1≤n≤105,
1≤K≤103,
给定的 n 个数均不超过 108

输入样例:

4 3
1 2 3 4

输出样例:

9

思路

背包的常见套路

一般状态表示的时候,把限制放到维数里面,一般有几个限制,就用几维的状态表示。

朴素解法

(存在问题,大约可以过掉8个数据,时间复杂度大)

背包解法(一堆集合,从里面选出来一定量的东西,然后在满足一定的条件下,求一个最优解的问题)

疑难杂题笔记——倍数问题

优化

可以把其中的一些数字给抹掉
因为所有取模k 得到的一样的数,可以当作一个集合来看,都是等价的;
所以说可以取最大的三个来代表 这个集合里面的数;

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

const int N = 1010;
vector<int> a[N];  //用来存放余数一样的数字的集合
int n,m;
int f[4][N]; //从后往前遍历,可以省略中间那一维的遍历 

int main()
{
    scanf("%d%d",&n,&m);
    
    for(int i = 0; i < n;i++)
    {
        int x;
        scanf("%d",&x);
        a[x % m].push_back(x);
    }
    
    memset(f,-0x3f,sizeof f);  //状态函数全部置为负无穷
    f[0][0] = 0;
    
    for(int i = 0;i < m;i++)
    {
        sort(a[i].begin(),a[i].end());
        reverse(a[i].begin(),a[i].end());  //转化为从大到小的
        
        for(int u = 0;u < 3 && u < a[i].size();u++)  //遍历灭一个集合的最大值
        {
            int x = a[i][u];
            for(int j = 3;j > 0;j--)  //遍历第一维:从几个数字中选择
                for(int k = 0;k < m;k++)  //遍历第三维
                    f[j][k] = max(f[j][k],f[j  - 1][(k - x % m + m) % m] + x);
                    
        }
    }
    
    printf("%d\n",f[3][0]);
    return 0;
 
}
相关标签: 算法学习 算法

上一篇:

下一篇: