题目描述
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
Hint
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
题目分析
暴力做法是枚举ABCD中的每个数,四重循环,时间复杂度O(n4),太慢。所以我们枚举A+B,构造出一个n^2大小的数组并排序,然后再枚举C+D,枚举C和D的时候计算它的相反数在A和B中出现多少次(也就是计算一个数在有序数列中第一次和最后一次出现的位置),即二分查找左边界和右边界。复杂度为O(n2logn2)
注意
若某个-(C+D)在A+B中不存在,则需返回-1,在主函数里判断是否返回-1,若是则不能将范围段加入。
代码
#define _CRT_SECURE_NO_WARNINGS
#define _ ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
//#include <bits/stdc++.h>
#include <iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
//快读
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch>'9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return x * f;
}
int A[4009];
int B[4009];
int C[4009];
int D[4009];
int AB[16000009];//A*B
int find_left(int x, int n) {//找到>=x的第一个位置
int l = 0, r = n * n - 1, ans = -1;
while (l <= r)
{
int mid = (l + r) >> 1;//右移比/2下取整快
if (AB[mid] == x)
{
ans = mid;
r = mid - 1;
}
else if (AB[mid] > x) r = mid - 1;
else l = mid + 1;
}
return ans;//返回-1代表不存在
}
int find_right(int x, int n) {//找到<=x的最后位置
int l = 0, r = n * n - 1, ans = -1;
while (l <= r)
{
int mid = (l + r) >> 1;//右移比/2下取整快
if (AB[mid] == x)
{
ans = mid;
l = mid + 1;
}
else if (AB[mid] > x) r = mid - 1;
else l = mid + 1;
}
return ans;//返回-1代表不存在
}
int main()
{
int n;//数列中数字的个数
cin >> n;
for (int i = 0; i < n; i++) {
A[i] = read();
B[i] = read();
C[i] = read();
D[i] = read();
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
AB[i * n + j] = A[i] + B[j];
}
sort(AB, AB + n * n);//对A*B排序
int cur;
int ans=0;
int left, right;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cur = (C[i] + D[j]) * (-1);
right = find_right(cur, n);
if(right !=-1)
ans += (right - find_left(cur, n)+1);//组合数量等于(右-左+1)
}
}
cout << ans;
return 0;
}