题目
给你一个长度为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)
思路
初版思路(显示RE/问了下后也可能是TLE)
一开始我的思路是,设置两个数组nums和items。
nums储存某个数字出现的个数,items储存所有不同的数字。
扫描一遍样例输入中第二行的数组,其中,nums里某个特定的数字的下标与items里面该下标对应的出现次数是相同的。
落实到代码就RE了,报错看不懂一点,这个思路不行。
二版思路(AC)
有了这个公式,我们就可以将样例输入第二行所有千奇百怪的数字全部映射到以内。
再取俩相加取k模,得到的结果与没有映射相同。
所以可以用一个大小为k的数组,下标对应的取模后的结果,元素代表出现次数。
这样将取模次数相乘就得到数对个数了。
什么时候取k模为0?
(1) &&
(2) 时, &&
(3) &&
注意,当 时,是数组的同一个下标的元素相乘,但题目要求的是选取两个不同的数。
如果数组一个下标的元素直接乘自己,就相当于把数对的两个位置都是自己的情况记了两次,但题目说明和是同一种选法。所以相当于多记了一次。
因此,对于 的情况,数对个数是 ,也就是n选2进行组合。
也要注意,当k为奇数和k为偶数,取模后相同的次数是不一样的,奇1偶2。
当然可以将k为奇数和偶数分开讨论,不过有更聪明的循环方式,不用刻意区分k是奇是偶。
代码
//1:不是最优版
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int n,k,tmp,count,j,half;
count=0;
cin>>n>>k;
vector<int> vec(k,0);
for(int i=0;i<n;i++)
{
cin>>tmp;
vec[tmp%k]+=1;
}
count+=vec[0]*(vec[0]-1)/2;
if(k%2==0){
half=k/2;
count+=vec[half]*(vec[half]-1)/2;
}
else half=k/2+1;
for(int i=1;i<half;i++)
{
count+=vec[i]*vec[k-i];
}
cout<<count<<endl;
return 0;
}
//2.最简单版本
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int n,k,tmp,count,j;
count=0;
cin>>n>>k;
vector<int> vec(k,0);
for(int i=0;i<n;i++)
{
cin>>tmp;
vec[tmp%k]+=1;
}
for(int i=0;i<k;i++)
{
j=(k-i)%k;
if(j<i)break;
else if(j>i)
count+=vec[i]*vec[j];
else
count+=vec[i]*(vec[i]-1)/2;
}
cout<<count<<endl;
return 0;
}
在最优版代码里,设置了变量j,一般思路是j=(k-i)但再对k取模后,但i=0时,j可以与i同时为0。
所以只需要再判断i和j的大小关系就可以了。
当然在j>i时相当于整个数组已经遍历完全,break结束程序。