题目描述
给你一个长度为n的数组和一个正整数k,问从数组中任选两个数使其和是k的倍数,有多少种选法
对于数组a1=1 , a2=2 , a3=2而言:
(a1,a2)和(a2,a1)被认为是同一种选法;
(a1,a2)和(a1,a3)被认为是不同的选法。
输入数据
第一行有两个正整数n,k。n<=1000000,k<=1000000 第二行有n个正整数,每个数的大小不超过1e9
输出数据
选出一对数使其和是k的倍数的选法个数
样例输入
5 6
1 2 3 4 5
样例输出
2
样例说明
a1+a5=6,a2+a4=6,都是6的倍数
所以符合条件的选法有(1,5),(2,4)
解题思路1
- 时间复杂度较高的解法
- 对数组进行两层循环遍历,0<i<n和i<j<n
- 一一列举每一对(ai,aj)并判断
- 判断:(ai+aj) % k == 0,若成立,解法+1
- 时间复杂度O(n2)
代码1
//两层的循环 时间复杂度较高
#include <iostream>
#include <cstring>
using namespace std;
int n, k;
int a[1000001];
int main()
{
memset(a, 0, sizeof(a)); //初始化
cin >> n >> k;
long long ans = 0;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if ((a[i] + a[j]) % k == 0)
ans++;
}
}
cout << ans;
return 0;
}
解题思路2
-
下面进行优化枚举对象
-
不难理解:
(ai+aj)%k=0
可以转化为(ai%k + aj%k) %k = 0
如: (31+5)%6 =0 -> (31%6+5%6)%6=0 -
由此,我们可以把余数相同的数组元素合并为一个子集cnt,然后把子集cnt作为枚举对象
-
那么对于组合问题,就是两个之和为k的cnt相乘
-
这里要特别注意i=0和i=k/2的情况,因为相加的对称性,这两种情况下,在循环中,是重复算了两遍的。
代码2
#include <iostream>
#include <cstring>
using namespace std;
long long cnt[1000001];
int main()
{
long long n, k;
cin >> n >> k;
while (n--)
{
int temp;
cin >> temp;
cnt[temp % k]++; //当前数字对k取余
}
long long ans;
//注意对i=0以及i=k/2要特判
ans = cnt[0] * (cnt[0] - 1) / 2;
for (int i = 1; i < (k + 1) / 2; i++)//从1~(k + 1) / 2遍历
ans += cnt[i] * cnt[k - i];
if (k % 2 == 0)
ans += cnt[k / 2] * (cnt[k / 2] - 1) / 2;
cout << ans;
return 0;
}
小结
- 对于枚举算法的优化,可以从以下两点进行考虑:优化模型—针对问题特征,优化对象模型;优化过程—针对对象特征,优化列举和验证过程
- 若文章存在问题,还请各位大佬批评指正,共同进步。