题目
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
Input
第一行:n(代表数列中数字的个数)(1≤n≤4000)。
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)。
Output
输出不同组合的个数。
Sample Input
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
Sample Output
5
思路
如果直接枚举ABCD中的数字,则复杂度为O(n^4),复杂度过高。这样做的想法是计算A+B+C+D=0。
为了少枚举点,降低复杂度,可以枚举A和B,然后再枚举C和D,则复杂度为O(n^2)。而这样做的想法是计算A+B=-(C+D)。
首先枚举A和B,计算A+B的和,并储存在数组e[]中,并使其为有序数组。然后再枚举C和D,计算C+D的相反数在e[]中出现多少次。在计算相反数出现多少次时,采用二分法。
进行二分时,左右边界l、r分别取数组e[]的左右边界,当l<r时,循环,取mid=(l+r)/2位置的值,若为要找的值,则查看其左右是否也是要找的值,直到找完其左右所有符合要求的点,返回找到的点的数目;若比要找的值小,l更新为mid+1;若比要找的值大,r更新为mid-1。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
int a[4005],b[4005],c[4005],d[4005],e[4000*4000+5];
int n;
int find(int x){
int l=0,r=n*n,leftx=0,rightx=0;
while(l<=r){
int mid=(l+r)/2;
if(e[mid]==x){
while((mid+rightx)<n*n&&e[mid+rightx]==x){
rightx++;
}
while((mid-leftx)>=0&&e[mid-leftx]==x){
leftx++;
}
return leftx+rightx-1;
}
else if(e[mid]<x)
l=mid+1;
else if(e[mid]>x)
r=mid-1;
}
return 0;
}
int main() {
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
}
int k=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
e[k]=a[i]+b[j];
k++;
}
}
int sum=0;
sort(e,e+n*n);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
sum+=find(-(c[i]+d[j]));
}
}
printf("%d",sum);
return 0;
}