倪文迪陪你学蓝桥杯2021寒假每日一题:1.21日(2018省赛A组第9题)

2021年寒假每日一题,2017~2019年的省赛真题。
本文内容由倪文迪(华东理工大学计算机系软件192班)和罗勇军老师提供。
后面的每日一题,每题发一个新博文,请大家每天博客蓝桥杯专栏: https://blog.csdn.net/weixin_43914593/category_10721247.html

提供C++、Java、Python三种语言的代码。

2018省赛A组第9题“倍数问题” ,题目链接:
http://oj.ecustacm.cn/problem.php?id=1366
https://www.dotcpp.com/oj/problem2277.html

1、题目描述


小葱给了你 n 个数,希望你从这 n 个数中找到三个数。使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。
输入:第一行包括 2 个正整数 n, K。第二行 n 个正整数,代表给定的 n 个数。
1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。
输出:输出一行一个整数代表所求的和。


2、题解

   先从暴力法开始思考。从n个数中找出任意3个数求和,看是否为k的倍数,其中最大的和就是答案。从n个数中拿3个数,有 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n1)(n2)种情况,而 n   < = 1 0 5 n <= 10^5 n<=105,超时。
  题目看起来很像背包(网上有用背包的题解),但是背包肯定要同时遍历所有的 n n n k k k,复杂度大于 O ( n k ) O(nk) O(nk),会超时。
   怎么办?注意到题目的要求有点怪:“三个数的和是 K 的倍数”。设三个数是a、b、c,符合条件的是:
   ( a + b + c ) % k = 0 (a+b+c)\%k=0 (a+b+c)%k=0,即三个数的和对k求余数结果是0。
   或者写为:
   ( a % k + b % k + c % k ) % k = 0 (a\%k+b\%k+c\%k)\%k=0 (a%k+b%k+c%k)%k=0
   这样,就把在n个数中搜三个数 a 、 b 、 c a、b、c abc,转化为在 k k k个数中搜 a 、 b 、 c a、b、c abc的余数。余数只有 k k k个,由于 n n n k k k大很多,复杂度能大大下降。
  原来这是一道数论题!
  具体做法是:
  (1)把 n n n个数对 k k k求余,按 % k \%k %k的结果分个类,记录该余数对应的最大三个数。

0abc
1...
2...
3...
....
....
....
k-1...

  (这个表格参考了https://blog.csdn.net/weixin_44290841/article/details/105788802
  (2)用 i 、 j i、j ij循环暴力枚举前2个余数 a % k 、 b % k a\%k、b\%k a%kb%k,第三个余数 t m p = c % k tmp=c\%k tmp=c%k等于:
   t m p = ( k − i + k − j ) % k tmp=(k-i+k-j)\%k tmp=(ki+kj)%k
  复杂度分析:用 i 、 j i、j ij k k k循环2次,复杂度 O ( k 2 ) O(k^2) O(k2),题目中 k k k=1000,非常好。

3、Python代码

  下面的Python代码完全重复了上面的解释。

#http://oj.ecustacm.cn/ User: 08195555
n, k = map(int,input().split())
r = [[0] * 3 for i in range(k)]     #记录余数,每个余数记录最大的三个数a、b、c

a = input().split()
# 输入数据就分组 同余一组 维持每组最大的三个数 用插入排序
for i in range(len(a)):    
    re = int(a[i]) % k
    if int(a[i]) > r[re][0]:
            r[re][2], r[re][1], r[re][0] = r[re][1], r[re][0], eval(a[i])
    elif int(a[i]) > r[re][1]:
            r[re][2], r[re][1] = r[re][1], eval(a[i])
    elif int(a[i]) > r[re][2]:
            r[re][2] = eval(a[i])
 
Max = 0
# 按照余数枚举
for i in range(k):
    for j in range(i, k):
        tmp = (k - i + k - j) % k
        v1 = r[i][0]       #a的余数 
        if i == j:
            v2 = r[i][1]   #如果b的余数和a的余数相同
            if i == tmp:   #如果c的余数和a也相同
                v3 = r[i][2]
            else:
                v3 = r[tmp][0]
        else:              #如果b的余数和a的余数不同
            v2 = r[j][0]
            if i == tmp:
                v3 = r[i][1]
            elif j == tmp:
                v3 = r[j][1]
            else:
                v3 = r[tmp][0]
        if v1 + v2 + v3 > Max:
            Max = v1 + v2 + v3
 
print(Max)

4、C++代码

   倪文迪的话:“主要从k入手,可以把所有的数先排序,再根据%k结果分个类。然后暴力枚举其中两个,另外一个根据三者之和%k为0的性质得出。由于栈的特殊性,排序后,后入栈的必定大,因而和最大。枚举时可以人为规定三者为不减顺序,可以压缩一点时间。”
  下面是倪文迪提供的代码。他存同余数组,没有用上面python代码中的普通数组,而是用了一个技巧:用栈数组。请对照代码仔细理解。

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
stack<int> st[N];  //用栈数组来存同余
int a[100010];
int main(){
	int n, k; cin >> n >> k;
	for(int i=1;i<=n;i++)  cin >> a[i];

	sort(a + 1, a + n + 1);         //先从小到大排序,然后用栈来存同余的数
	for(int i = 1 ; i <= n ; i++)   //进栈是从小到大,出栈按从大到小
		st[a[i] % k].push(a[i]);

	int res = 0;
	for(int i = 0 ; i < k ; i++){
		if(!st[i].size())
            continue;
		for(int j = i ; j < k ; j++){
			if(!st[j].size())
                continue;
			int rm = (k - i - j + k) % k, tmp = 0, ans1, ans2, ans3;
			if(rm < j)
                continue;
			if(st[i].size()){
				ans1 = st[i].top();
				tmp += ans1;
                st[i].pop();
				if(st[j].size()){
					ans2 = st[j].top();
					tmp += ans2;
					st[j].pop();
					if(st[rm].size()){
						ans3 = st[rm].top();
						tmp += ans3;
						st[rm].pop();
						res = max(res, tmp);
						st[rm].push(ans3);
					}
					st[j].push(ans2);
				}
				st[i].push(ans1);
			}
		}
	}
	cout << res << endl;
	return 0;
}

5、Java代码

//http://oj.ecustacm.cn/ User: mingyuemy
import java.io.*;
import java.util.*;
public class Main{        
    public static void main(String[] args) throws IOException {
        StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        st.nextToken();
        int n=(int)st.nval;
        st.nextToken();
        int k=(int)st.nval;
        int[][] a=new int[k][3];
        for(int i=0;i<n;i++) {
            st.nextToken();
            int t=(int)st.nval;
            int r=t%k;
            if(t>a[r][0]) {
                a[r][2]=a[r][1];
                a[r][1]=a[r][0];
                a[r][0]=t;
            }else if(t>a[r][1]) {
                a[r][2]=a[r][1];
                a[r][1]=t;
            }else if(t>a[r][2]) {
                a[r][2]=t;
            }
        }
         
        long ans=0;
        for(int x=0;x<k;x++) {
            for(int y=0;y<k;y++) {
                int z=(k+k-x-y)%k;
                int v1=0,v2=0,v3=0;
                v1=a[x][0];
                if(y==x) {
                    v2=a[y][1];
                    if(z==x) {
                        v3=a[z][2];
                    }else {
                        v3=a[z][0];
                    }
                }else {
                    v2=a[y][0];
                    if(z==x) {
                        v3=a[z][1];
                    }else if(z==y) {
                        v3=a[z][1];
                    }else {
                        v3=a[z][0];
                    }
                }
                if(v1+v2+v3>ans) ans=v1+v2+v3;
            }
        }
        System.out.println(ans);         
    } 
}
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗勇军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值