资源限制
内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s
问题描述
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。
输入格式
从标准输入读入数据。
第一行包括 2 个正整数 n, K。
第二行 n 个正整数,代表给定的 n 个数。
输出格式
输出到标准输出。
输出一行一个整数代表所求的和。
样例入
4 3
1 2 3 4
样例输出
9
样例说明
选择2、3、4。
数据约定
对于 30% 的数据,n <= 100。
对于 60% 的数据,n <= 1000。
对于另外 20% 的数据,K <= 10。
对于 100% 的数据,1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include <xxx>
不能通过工程设置而省略常用头文件。
提交程序时,注意选择所期望的语言类型和编译器类型。
思路
直接暴搜显然会超时。看数据范围暗示此题可用dp。
dp的难点便是如何表示和计算状态。其实说什么最优子结构这些感觉都很虚。重要的是先确定状态表示和状态计算。状态表示一般是用一个二维或者多维数组来表示某个集合。该集合有独特的属性,如求最大值,那么每个集合的属性便是最大值。而状态计算便是,做到不重复不遗漏的“计算”每一个集合的子集。这里的计算根据实际情况来确定是如何计算。如找最大值,那么对于每一个子集都取最大值,那么最终的集合也就是最大值。然后确定如何对子集如何进行计算便可。
那么对于此题,在n个数中取三个数。显然状态太多,很难确定。这里有个很好的办法来应付此类整除倍数题。因为题目中只要选三个数,且是要求最大的。那么我们通过每个数对于k的余数,来将这些数分成各个集合。%k的余数只有0~k-1。如此分类,便把看似众多无关的状态转化成由余数唯一确定的逐个集合们。最后之取这些集合们的前三大的数来组合,便大大节约了时间。
其次还要知晓到
(a+b+c)%k=0 等价于 (a%k+b%k+c%k)%k=0
即多数之和%k,等价于多数先各自%k再求和再%k
如此下来,有了如何处理这些集合的子集的方向。
设dp[i][j]表示选i个数 余数为j的状态。题即要求dp[3][0]。
剩下的详见代码
Code1
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e3+10;
vector<int> a[M];
int dp[4][N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int t;
cin>>t;
a[t%k].push_back(t);//以余数为状态 ,余数相同的数为同一状态
}
memset(dp,-0x3f,sizeof (dp));//初始化
dp[0][0]=0;// 取0个数余数为0的值为0
for(int i=0;i<k;i++)//枚举余数 0~k-1
{ //降序排列
sort(a[i].begin(),a[i].end(),greater<int>());
for(int u=0;u<3&&u<a[i].size();u++)//每种余数的集合只选前三大的数即可
{
int x=a[i][u];//获得余数为i的数
for(int j=3;j>=1;j--)// 枚举选的数
for(int m=0;m<k;m++)//枚举余数0~k-1
dp[j][m]=max(dp[j-1][(m-x%k+k)%k]+x,dp[j][m]);
//选x和不选x 若选x则选j-1个数时候余数应该是 m-x%k 但是显然可能为负数
//假如m=1,k=3,x=5,x%k=5%3=2。m-x%k=1-2=-1,余数显然不会为负数。之所以为负数要理解成上一个状态余数比k小了1
//即3-1=2 2%3=2。所以选j-1个数的余数应该为2
}
}
cout<<dp[3][0];
}
Code2
将此题类比于01背包问题,显然每个数只有选或者不选两个状态。
#include<bits/stdc++.h>
const int maxn=1e5+5;
using namespace std;
int n,k;
int dp[5][1005]={0},w[maxn],v[maxn];//dp[i][j]为选i个数,对k的余数为j对应的最大和。题目所求即dp[3][0]
//类似于背包问题,此处的余数即类似于物品的体积。最终物品体积之和%k也要=0
//(a+b+c)%k==0 d等价于(a%k+b%k+c%k)%k==0
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
w[i]=v[i]%k;//以余数作为体积
}
for(int i=1;i<=n;i++)
{
for(int j=2;j>=0;j--)//枚举选择的数
{
for(int t=k-1;t>=0;t--)//余数的范围是k-1~0 枚举余数相当于背包问题中的枚举体积
{
if((j==0&&t==0)||dp[j][t])//选了0个和模k为0 or 已经选好了j个和模k为t,更新选j+1个时dp值
dp[j+1][(t+w[i])%k]=max(dp[j][t]+v[i],dp[j+1][(t+w[i])%k]);//选v[i]和不选v[i]
}
}
}
cout<<dp[3][0]<<endl;
return 0;
}