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,那么每次left和right的变化不要忘了在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;
}