题意
牛客网
给定n行4列的整数,从每一列各选择一个数加起来等于0的组合有多少种?
思路
这题很容易让人想到枚举,要让四个数字的和等于0,从前三列选择了三个数以后第四列要找的数就是确定的,可以不断地枚举找到答案,但这样时间复杂度为
O
(
n
4
)
O(n^4)
O(n4),这可不是我们负担的起的。
所以在枚举的基础上可以加入二分,确定了前三列的数字以后,对第四列进行排序,之后再从第四列种二分查找判断有多少个我们要找的数即可。
但即使是这样,我们对四列数据的枚举仍会带来大量重复的计算,所以我们可以对第一、二列的数据两两求和,形成一个新的数组,对第三、四列进行一样的操作,这样我们就只剩下两个数组,每个数组的长度为
n
2
n^2
n2。
之后再对第二个数组排序,然后对第一个数组进行枚举,再从第二个数组里二分来统计我们要找的数的个数,把答案相加即可。
代码
*因为正在学二分,所以我把代码里的lowerbound和upperbound自己写了一下,没拿出来验证是不是对的,但是能AC
#include <iostream>
#include <algorithm>
using namespace std;
int n; //数据的行数
int num[4005][4];
int part1[16000025]; //第一列和第二列的和
int part2[16000025]; //第三列和第四列的和
//返回x在part2中第一次出现的下标
//相当于algorithm中的lower_bound
int findLow(int x) {
int left = 0, right = n * n - 1, mid;
while (left <= right) {
mid = (left + right) >> 1;
if (part2[mid] < x)left = mid + 1;
else right = mid - 1;
}
return left;
}
//返回x在part2中最后一次出现的下标的下一个
//相当于algorithm中的upper_bound
int findUp(int x) {
int left = 0, right = n * n - 1, mid;
while (left <= right) {
mid = (left + right) >> 1;
if (part2[mid] <= x)left = mid + 1;
else right = mid - 1;
}
return left;
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 4; ++j) {
cin >> num[i][j];
}
}
//把第一、二列和第三、四列的数两两相加
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
part1[(i - 1) * n + j - 1] = num[i - 1][0] + num[j - 1][1];
part2[(i - 1) * n + j - 1] = num[i - 1][2] + num[j - 1][3];
}
}
sort(part2, part2 + n * n);
int res = 0; //记录答案
//对第一列枚举,从第二列中二分统计能让和为0的数的个数
for (int i = 0; i < n * n; ++i) {
res += findUp(-part1[i]) - findLow(-part1[i]);
}
cout << res;
}