#2020.01.14训练题解#二分入门(G题)

题源POJ-2785

POJ-2785-4 Values whose Sum is 0

Description
The SUM problem can be formulated as follows: given four lists A, B, C, D of integer values, compute how many quadruplet (a, b, c, d ) ∈ A x B x C x D are such that a + b + c + d = 0 . In the following, we assume that all lists have the same size n .

Input
The first line of the input file contains the size of the lists n (this value can be as large as 4000). We then have n lines containing four integer values (with absolute value as large as 228 ) that belong respectively to A, B, C and D .

Output
For each input file, your program has to write the number quadruplets whose sum is zero.

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
Sample Explanation: Indeed, the sum of the five following quadruplets is zero: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).

题意

  • 输入一个N,代表接下来有N行数据
  • 第二行开始每行输入的数分别对应A、B、C、D
  • 即第一列的都是A,第二列的都是B,以此类推
  • 题意要求,从A列选一个数+B列选一个数+C列选一个数+D列选一个数,其和等于0
  • 输出满足题意要求的ABCD有几种取法

题解

  • 这题最关键的思想就是化繁为简,合多为少
  • A、B、C、D共有四列,各选一个数,看似很难二分
  • 但是既然是求四个数的和,为何不先两两合并
  • 比如把一二列求和三四列也先求和,就得到两个数组,再进行遍历二分即可
  • 注意两两求和得到的数组下标得开得够大,N最大是4000,那数组得开4000*4000以上
  • sum1(一二列求和的数组)进行遍历每次二分寻找符合和为零sum2(三四列求和的数组)
  • 由于两两求和可能值相同,所以找到符合题意sum2时,得往前往后分别遍历是否还有符合的,没有就break
  • 上述步骤非常容易遗漏,详见代码行注释
  • 注意循环条件是right>=left,那么每次leftright变化不要忘了在mid的基础上进行-1/+1
  • 不然最后left和right都等于mid,两者还是满足循环条件right>=left,进入死循环
  • 遇到符合的用一个初始化为0的变量进行计数即可得到一共有多少种取法,最后输出即可

涉及知识点

  • 二分 算法(此题属于整数二分)
  • 对于二分的算法-详见链接博客介绍二分

AC代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int put[4010][10],sum1[16000010],sum2[16000010];
int main()
{
    int n,mid;
    while(~scanf("%d",&n))
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d %d %d %d",&put[i][0],&put[i][1],&put[i][2],&put[i][3]);
        }
        int first=0,second=0;
        for(int i=0;i<n;i++)
        {
        	for(int j=0;j<n;j++)
            {
                sum1[first]=put[i][0]+put[j][1];
                first++;//第一列和第二列各个元素相加存入sum1 
                sum2[second]=put[i][2]+put[j][3];
                second++; //第三列和第四列各个元素相加存入sum2 
            }
		}  
        sort(sum2,sum2+second);//对第三列和第四列的和数组升序排序 
        int ans=0;
        for(int i=0;i<first;i++)//排序后sum1遍历,sum2二分查找 
        {
            int left=0;
            int right=second-1;
            while(right>=left)
            {
                mid=(left+right)/2;
                if(sum1[i]+sum2[mid]==0)
                {
                    for(int j=mid;j<second;j++)
                    {
                        if(sum1[i]+sum2[j]!=0) break;
                        else ans++; 
                    }
					/*
						对于一对前两列的和,需要配某值,排序后可能存在连续多组和都为该值
						正逆遍历有则加,无则break,因为出现无,则接着必然无 
					*/
                    for(int j=mid-1;j>=0;j--)
                    {
                        if(sum1[i]+sum2[j]!=0) break;
                        else ans++;
                    }
                    break;
                }
                if(sum1[i]+sum2[mid]<0) left=mid+1;
                else right=mid-1;
                /*
					二分改left、right时,勿忘+1和-1
					不然会造成重复比较和死循环 
					因为这是整数二分,且循环条件是right>=left!!!
				*/
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值