枚举算法——数组配对

问题描述:
  给定一个长度为n的数组arr和一个正整数k,问从数组中任选两个数使其和是k的倍数有多少种选法。
注: 对于数组a1=1,a2=2,a3=3来说,(a1,a2)和(a2,a1)是同一种选法,(a1,a2)和(a1,a3)是不同的选法。
输入: 第一行有两个正整数n和k,n<=1e6,k<=1000。
第二行有n个正整数,每个数的大小不超过1e9。
输出: 选出一对数使其是k的倍数的选法个数

输入样例:
5 6
1 2 3 4 5
输出样例:
2
思路1: 暴力枚举

for(int i=0;i<n;i++)
	for(int j=i+1;j<n;j++)
			if( (arr[i]+arr[j])%k==0 )
				cnt++;

这个应该很容易想到,枚举所有数据,将每个数都与它后面的数求和再判断是否是k的倍数即可。但是它的时间复杂度是O(n^2)。当n=1e6时,循环验证次数达到了1e12,这显然不是我们想要的。
思路2: 优化枚举
这里需要将判别式进行一个转换,在过程中取余和最后取余是不会改变结果的:
     (a[i]+a[j])%k=0   <---->   (a[i]%k+a[j]%k)%k=0

根据新的判别式我们可以将arr数组中的数字进行处理,即

for(int i=0;i<n;i++)
	b[arr[i]%k]++;

这样就得到了一个处理过后的新数组b[0]~b[k-1],表示的是原来所有数%k 的结果的出现次数。我们可以推导一下b数组的建立。
比如给定一组输入:
5 6
1 4 5 2 7
推导b数组:
b[1%6]=b[1];
b[4%6]=b[4];
b[5%6]=b[5];
b[2%6]=b[2];
b[7%6]=b[1];
即:b[0]=0; b[1]=2(1%6=1,7%6=1); b[2]=1; b[3]=0; b[4]=1; b[5]=1;
我们容易看出 b[i] 和 b[(k-i)%k] 中的数可以求和为k的倍数(0<=i<k)。
比如:上面例子中b[1]=2,b[5]=1,组合个数为b[1] * b[5]=2;即 (1+5)%6=0和(7+5)%6=0。
以此类推······

当 i==(k-i)%k时,表示b[i]中的数自我组合,比如5%5=0,10%5=0。b[0]=2.组合个数为b[0]*(b[0]-1)/2=1。即5+10=15。
当 b[i] 的 i>(k-i)%k 时,新的组合是前面的组合交换顺序得到的,比如前面求过(a1,a2)这时是(a2,a1),这是不符合题目要求的,我们就可以终止枚举了。
所以由新的表达式我们可以将枚举对象变化为b[0] ~b[k-1],由原来的单纯枚举数据变为枚举(数据%k)后数出现次数。时间复杂度优化到了O(k)。

for(int i=0;i<k;i++)
{
	long long ans=0;
	int j=(k-i)%k; //b[0]~b[k-1],b[1]和 b[k-1]可以组合成 k,以此类推
	if(i>j)  //i>j后的组合都是出现过的,不符合要求
		break;
	else if(j==i)  //自我组合
		ans+=1LL*b[i]*(b[i]-1)/2; 
	else  //b[i]和b[j]组合
		ans+=1LL*b[i]*b[j];  
	//1LL表示将1强制转为long long,避免相乘超int数据范围
}

完整代码:

  • 暴力枚举:
#include<stdio.h>
const int maxn=1e6+5;
int arr[maxn];
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
		scanf("%d",&arr[i]);
	long long cnt=0;
	for(int i=0;i<n;i++)//暴力枚举
		for(int j=i+1;j<n;j++)
			if((arr[i]+arr[j])%k==0)
				cnt++;
	printf("%lld\n",cnt);
	return 0;
}
  • 优化枚举
#include<stdio.h>
#include<string.h>
const int maxn=1e6+5;
int arr[maxn];//arr为全局变量,默认已经全部初始化为0
int main()
{
	memset(arr,0,sizeof(arr));//批量初始化arr数组为0
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
	{
		int temp;
		scanf("%d",&temp);
		arr[temp%k]++;//计数
	}
	long long cnt=0;
	for(int i=0;i<k;i++)//优化枚举
	{
		int j=(k-i)%k;
		if(i>j)
			break;
		else if(i==j)
			cnt+=1LL*arr[i]*(arr[i]-1)/2;
		else
			cnt+=1LL*arr[i]*arr[j];
	}
	printf("%lld\n",cnt);
	return 0;
}
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值