标题:倍数问题
【题目描述】
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 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。
n的范围三层循环会超时,所以要想办法优化枚举。
优化枚举几种思路:1.二分2.空间换时间3.减少层数
(2017年蓝桥杯切巧克力问题就是二分枚举的优化)
此题是减少层数
由同余模定理,(a+b+c)%k =(a%k+b%k+c%k)%k=0,用其缩小层数
所以我们只需要确定前两个数a%k,b%k则第三个数c%k就能确定。即限定了c是k的倍数,缩小了范围。
例如k=3,a=4,b=6,则a%k=1,b%k=0,要想满足(a%k+b%k+c%k)%k=0,所以c%k为2,这样c的范围就确定了,为2,5,11…
但是如果还是按照遍历的思路,把第一个数从1~10^5, 第二个数从1~10^5,第三个数由于前两个确定于是第三个的范围就定了,但是还是三层循环,且前两次范围还是10的五次方。
那么有没有什么优化办法呢?
把每个数录入时就模k,再用类似哈希拉链法,把模完k前三大的数存起来,比如录入1000,4,1,6,999,5。令k=3,则在模k等于1的位置存的就是1000,4,1这三个从大到小的数,在模k等于0的位置存的就是999,6,两个个从大到小的数。那为什么要保存前三大的数呢?因为我们要求abc相加%k=0,其中abc相加最大的值,那么有可能abc模k的值是相同的,但我们只要求最大,所以保存前三个最大的即可。
经过模k的哈希函数,我们成功把循环范围降到了k。
代码如下
int list[1005][3];
int main()
{
//(a+b+c)%k = (a%k+b%k+c%k)%k
int n,k;
int num;
cin>>n>>k;
while(n--)
{
cin>>num;
//把输入的数映射哈希,在数模k相同的情况下,取前三个大的数
if(num >= list[num%k][0])//比三个里最大的都大
{
list[num%k][2] = list[num%k][1];
list[num%k][1] = list[num%k][0];
list[num%k][0] = num;
}
else if(num < list[num%k][0] && num > list[num%k][1])//比第二个大比第一个小
{
list[num%k][2] = list[num%k][1];
list[num%k][1] = num;
}
else if(num < list[num%k][1] && num > list[num%k][2])//比第三个大比第一二个小
{
list[num%k][2] = num;
}
}
int max = 0x80000000;
int v1,v2,v3;//求的三个数
//枚举两个数,找满足条件第三个数
for(int i=0;i<=k;i++)//i为第一个数%k的余数
{
for(int j=i;j<=k;j++)//j为第二个数模k的余数
{
int t = (k-i+k-j)%k;//t为第三个数模k的余数
v1 = list[i][0];//第一个数已经确定
if(i == j)//如果i,j模k余数相等
{
v2 = list[i][1];//v2为v1后边的第一个数,v2v1模k余数相等
if(t == i)如果i,t模k余数相等
v3 = list[i][2];//v2为v1后边的第二个数,v3v1模k余数相等
else
v3 = list[t][0];
}
else
{
v2 = list[j][0];
if(t == i)
v3 = list[i][1];
else if(t == j)
v3 = list[j][1];
else
v3 = list[t][0];
}
if(v1+v2+v3 > max)
max = v1+v2+v3;
}
}
cout<<max<<endl;
}