题意

给定n行4列的整数,从每一列各选择一个数加起来等于0的组合有多少种?

思路

这题很容易让人想到枚举,要让四个数字的和等于0,从前三列选择了三个数以后第四列要找的数就是确定的,可以不断地枚举找到答案,但这样时间复杂度为,这可不是我们负担的起的。
所以在枚举的基础上可以加入二分,确定了前三列的数字以后,对第四列进行排序,之后再从第四列种二分查找判断有多少个我们要找的数即可。

但即使是这样,我们对四列数据的枚举仍会带来大量重复的计算,所以我们可以对第一、二列的数据两两求和,形成一个新的数组,对第三、四列进行一样的操作,这样我们就只剩下两个数组,每个数组的长度为
之后再对第二个数组排序,然后对第一个数组进行枚举,再从第二个数组里二分来统计我们要找的数的个数,把答案相加即可。

代码

*因为正在学二分,所以我把代码里的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;
}