题目描述:
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
请你帮帮他吧!
Input:
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
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
Output:
输出不同组合的个数。
sample output:
5
个人思路:
首先想到的就是枚举了,四层循环,枚举集合A、B、C、D中的每个元素,求和。逻辑上来看是合理的。但是O(n^4)的时间复杂度对于[0,4000]也就是4000 ^4是难以接受的。那么该怎么优化呢?
枚枚举A和B,然后枚举C和D,两重循环!
枚举C和D的时候计算它的相反数在A和B中出现多少次!
现在的复杂度已经是O(n^3)
还是接受不了
既然枚举已经优化过了,在想一下找相反数。
找相反数的个数是不是等价于计算一个数在有序数列中第一次和最后一次出现的位置!
有序数列找位置,二分~!
最终复杂度O(n^2 * logn)
可以接受!
代码实现:
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int a[4001];
int b[4001];
int c[4001];
int d[4001];
int p[8000001];
int total = 0;
int find_num(int x) {
int num = 0, l = 0, r = n * n;
while (l <= r) {
int mid = (l + r) >> 1;
if (p[mid] == x) {
int now1 = mid;
int cha = 0;
while (1) {
if (mid - 1 >= 0) {//往左找第一个位置
if (p[mid - 1] == x)
--mid;
}
if (now1 + 1 < n * n) {//往右找最后的位置
if (p[now1 + 1] == x)
++now1;
}
if (mid - now1 == 0 || cha == now1 - mid)//数量不变时退出
break;
cha = now1 - mid;//记录数量
}
return cha + 1;
}
if (p[mid] > x) {
r = mid - 1;
}
else
l = mid + 1;
}
return 0;
}
int main() {
cin >> n;
for (int j = 0; j < n; ++j) {
cin >> a[j] >> b[j] >> c[j] >> d[j];
}
int s = 0;
for (int i = 0; i < n; ++i) {//枚举A和B求和
for (int j = 0; j < n; ++j,++s) {
p[s] = a[i] + b[j];
}
}
sort(p, p + s);
for (int i = 0; i < n; ++i) {//枚举C和D并且找相反数
for (int j = 0; j < n; ++j) {
total = total + find_num(- c[i] - d[j]);
}
}
cout << total << endl;
return 0;
}