SDU_week4_B - 四个数列(二分)

题目描述

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值