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
小葱给了你 n 个数,希望你从这 n 个数中找到三个数。使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。
输入:第一行包括 2 个正整数 n, K。第二行 n 个正整数,代表给定的 n 个数。
1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。
输出:输出一行一个整数代表所求的和。
先从暴力法开始思考。从n个数中找出任意3个数求和,看是否为k的倍数,其中最大的和就是答案。从n个数中拿3个数,有 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n−1)(n−2)种情况,而 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 a、b、c,转化为在 k k k个数中搜 a 、 b 、 c a、b、c a、b、c的余数。余数只有 k k k个,由于 n n n比 k k k大很多,复杂度能大大下降。
原来这是一道数论题!
具体做法是:
(1)把 n n n个数对 k k k求余,按 % k \%k %k的结果分个类,记录该余数对应的最大三个数。
0 | a | b | c |
---|---|---|---|
1 | . | . | . |
2 | . | . | . |
3 | . | . | . |
. | . | . | . |
. | . | . | . |
. | . | . | . |
k-1 | . | . | . |
(这个表格参考了https://blog.csdn.net/weixin_44290841/article/details/105788802)
(2)用 i 、 j i、j i、j循环暴力枚举前2个余数 a % k 、 b % k a\%k、b\%k a%k、b%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=(k−i+k−j)%k
复杂度分析:用 i 、 j i、j i、j对 k k k循环2次,复杂度 O ( k 2 ) O(k^2) O(k2),题目中 k k k=1000,非常好。
下面的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)
倪文迪的话:“主要从k入手,可以把所有的数先排序,再根据%k结果分个类。然后暴力枚举其中两个,另外一个根据三者之和%k为0的性质得出。由于栈的特殊性,排序后,后入栈的必定大,因而和最大。枚举时可以人为规定三者为不减顺序,可以压缩一点时间。”
下面是倪文迪提供的代码。他存同余数组,没有用上面python代码中的普通数组,而是用了一个技巧:用栈数组。请对照代码仔细理解。
#include
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;
}
//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);
}
}