2018蓝桥杯C++ A组 倍数问题(枚举+同余模定理+哈希优化)

标题:倍数问题
【题目描述】
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值