题意
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
请你帮帮他吧!
输入输出格式
input:
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
output:
输出不同组合的个数。
输入输出样例
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:
5
样例输出解释:(-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
思路
- 此题刚开始以为是一个暴力枚举,就是枚举A,B,C,D中的每一个元素然后进行相加即可,那么复杂度是O(n4),是受不了的。
- 那么就进行少枚举一些点,是不是就可以了,将A和B进行一一枚举相加,C和D进行一一枚举相加,那么此时复杂度就是O(n2),是可以接受的,此时我们就可以,将A+B中的值和C+D中的值的相反数进行一一枚举相加,然后选择和为0的个数;
- 将A+B中的值取反,然后去C+D中去找这个数的个数;那么就可以使用整数二分,先将C+D进行升序排序,然后通过整数二分先找到第一个出现的位置,然后再找到最后一个出现的位置,相减,就可以得到一个值对应的组合数;遍历即可
总结
- 这种取数的问题,一眼看过去就是一个一个进行枚举,那么数据范围一大,特定超时,那么我们可以想办法进行使得枚举的点少一些,可以进行一些剪枝,排除一些情况的枚举;
- 此题在做的时候,没有认真读题,应该是不去重的,因为一个数列中,相同的数是按不同数算的;教训(认真读题,别放过一个字,教训,不然就是0,而且该0)
代码
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int last_location(int* p, int m)
{
int l = 0, r = n*n - 1,ans = -1;
while (l <=r)
{
int mid = (l + r) >> 1;
if (p[mid] == m)
{
ans = mid;
l = mid + 1;
}
else if (p[mid] > m) r = mid - 1;
else
l = mid + 1;
}
return ans;
}
int first_location(int* p, int m)
{
int l = 0, r = n*n - 1, ans = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (p[mid] == m)
{
ans = mid;
r= mid - 1;
}
else if (p[mid] > m) r = mid - 1;
else
l = mid + 1;
}
return ans;
}
int main()
{
cin >> n;
int* a = new int[n];
int* b = new int[n];
int* c = new int[n];
int* d = new int[n];
int* e = new int[n * n];
int* f = new int[n * n];
for (int i = 0; i < n; i++)
cin >> 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];
f[k] = c[i] + d[j];
k++;
}
sort(e, e + n * n);
sort(f, f + n * n);
int sum = 0;
for (int i = 0; i < n * n; i++)
{
int m = first_location(f, -e[i]);
int n = last_location(f, -e[i]);
if(n!=-1)
sum = sum + n-m+1;
}
cout << sum << endl;
return 0;
}