问题描述
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
【输入】
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
【输出】
输出不同组合的个数。
【样例输入】
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
【样例输出】
5
问题分析
1、A+B+C+D=0,可以转换为讨论A+B=-(C+D)
2、将AB数组依次加和形成数组a,CD数组依次加和形成数组b
3、排序a数组后,枚举b找它在a中的相反数的位置,计算得到相反数第一次和最后一次出现的位置
4、计算一个数在第一次和最后一次出现的位置时,用到二分思想
代码分析
mid=(l+r)>>1与mid=(l+r)/2的结果一样,但前者速度更快
#include<iostream>
#include<algorithm>
using namespace std;
int a[16000010],b[16000010];
int A[5000],B[5000],C[5000],D[5000];
int findl(int x,int n)//找到x的第一个位置
{
int l=0,r=n-1,ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;//速度快
if(a[mid]==x)
{
ans=mid;
r=mid-1;
}
else if(a[mid]>x) r=mid-1;
else l=mid+1;
}
return ans;
}
int findr(int x,int n)//找到x的最后一个位置,不存在返回-1
{
int l=0,r=n-1,ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid]==x)
{
ans=mid;
l=mid+1;
}
else if(a[mid]>x) r=mid-1;
else l=mid+1;
}
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>A[i]>>B[i]>>C[i]>>D[i];
}
int k=0,sum=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
a[k]=A[i]+B[j];
b[k]=C[i]+D[j];
k++;
}
}
sort(a,a+k);
for(int i=0;i<k;i++)
{
int min=findl(-b[i],k);
int max=findr(-b[i],k);
if(max>=min&&max!=-1&&min!=-1)
{
sum+=max-min+1;
}
}
cout<<sum<<endl;
}