数组配对+推理

题目描述

给你一个长度为n的数组A和一个正整数k,问从数组中任选两个数使其和是k的倍数,问有多少种选法?对于数组a1=1,a2=2,a3=2而言:
1.(a1,a2)和(a2,a1)是同一种选法;
2.(a1,a2)和(a1,a3)不是同一种选法;

input

第一行输入两个整数n,k;n<1000000,k<1000 第二行有n个正整数,每个正整数的大小不超过1e9;
5 6
1 2 3 4 5

output

输出多少种选法?
2

思路:

暴力枚举数组中任意两个元素的组合;使其满足(a[i]+a[j])%k==0,一定注意n种选法,n可能超过int,这是一个排列组合 \complement 2 n 2 \atop n =n(n-1)/2.

代码1(n²)

#include<stdio.h>
typedef long long ll;
const int MAXN=1e6+5;
int arr[MAXN];
int main()
{
	int n,k;//n,k分别代表数组个数和倍数 
	ll cnt=0;//容易超long long 
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)	scanf("%d",&arr[i]);
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
			if((arr[i]+arr[j])%k==0)	cnt++; 
	printf("%d",cnt);
	return 0;
}

当n为1e6时,n²就会是1e12,程序将会跑的很慢很慢。怎么来优化算法呢?这里提到一个公式(ai+aj)%k=0,(ai%k+aj%k)%k=0;这是解这个题的关键,那么就可以把问题缩小化,也就是求0~k-1中的数组对和是k即可。当把数组缩小后,会呈现一定的规律,bi和b(k-i)是一个数组对,很简单假设数组为6,7,8,9,10时,倍数为6,缩小数组后为0,1,2,3,4,那么第2个(从0开始的哦)和第4个就是成对出现的,因此数组对分配在数组两边。
(1).j<i是就不用判断了,因为数组对必须分配在数组两边。
(2).i=j在同一个集合里面找两个,也就是 \complement 2 n 2 \atop n
(3).i<j则是两个不同的集合,那么就跟映射对一样了。
具体看代码.

代码2(k)

#include<stdio.h>
typedef long long ll;
const int MAXN=1e6+5;
int arr[MAXN];
int main()
{
	int n,k;//n,k分别代表数组个数和倍数 
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
	{
		int x;
		scanf("%d",&x);
		arr[x%k]++;
	} 
	ll ans=0;//这里一定要注意次数爆int,从n中随机选取两个,在高中我们都学过组合吧,Cn2,也就是n(n-1)/2; 
	for(int i=0;i<k;i++)
	{
		int j=(k-i)%k;//i是前一半,j是数组的后一半
		if(j<i)	break;//只需枚举到一半就行,这个问题很像高斯求和一样
		if(i==j)
			ans+=(ll)arr[i]*(arr[j]-1)/2;//从同一个集合选取两个,也是Cn2;arr[i]*arr[j]可能超int范围 
		else
			ans+=(ll)arr[i]*arr[j];//不同集合的组合 
	}
	printf("%d\n",ans);
	return 0;
} 

这个题还是有很多需要注意的点,例如ai*aj容易爆int,所以要强制类型转换。

发布了39 篇原创文章 · 获赞 1 · 访问量 569

猜你喜欢

转载自blog.csdn.net/qq_45249273/article/details/104407110