[蓝桥杯2018初赛]倍数问题

题目描述

众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。
但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。
现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数
使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。

输入

第一行包括 2 个正整数 n, K。
第二行 n 个正整数,代表给定的 n 个数。
1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。

输出

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

样例输入
4 3
1 2 3 4
样例输出
9

分析:首先看这个题,从n个数中选三个数,且三个数相加的和是k的倍数,且满足倍数中最大。
那么这一看就是背包空间3,选数满足特殊要求,的01背包吧。

现在分析怎么才能构成k的倍数, 假设我们 k=3,要选一个数4,要让它构成3的倍数,然后要选另一个数,
那么另一个数要让它余数为零,则要找余数为(0 – x%k + k)%k的数,则推出两个数相加要求余数不变,则满足(当前余数 - x%k + k)%k的数。

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <vector>
  5 #include <iostream>
  6 using namespace std;
  7 
  8 const int N = 1010;
  9 
 10 int n, m;
 11 vector<int> a[N];
 12 int f[4][N];//4代表当前选的数,N代表余数 
 13 
 14 int main()
 15 {
 16     scanf("%d%d", &n, &m);
 17 
 18     for (int i = 0; i < n; i ++ )
 19     {
 20         int x;
 21         scanf("%d", &x);
 22         a[x % m].push_back(x);//将数按余数分组 
 23     }
 24 
 25     memset(f, -9999, sizeof f);
 26     f[0][0] = 0;
 27 
 28     for (int i = 0; i < m; i ++ )//枚举余数背包 
 29     {
 30         sort(a[i].rbegin(), a[i].rend());//从大到小排序 
 31 
 32         for (int u = 0; u < 3 && a[i].size() > u; u ++ )//每个分组最多选三个最大的 
 33         {
 34             int x = a[i][u];
 35 
 36             for (int j = 3; j >= 1; j -- )
 37                 for (int k = 0; k < m; k ++ ){//枚举余数 
 38 
 39                     f[j][k] = max(f[j][k], f[j - 1][(k - x%m + m) % m] + x);
 40                     //f[j - 1][(k - x%m + m) % m] + x保证相加后的余数还为k 
 41                 }
 42         }
 43     }
 44 
 45     printf("%d\n", f[3][0]);//输出我们要的余数为零的数 
 46 
 47     return 0;
 48 }
 49 

追求吾之所爱
原文地址:https://www.cnblogs.com/rstz/p/14391051.html