POJ2785 - Values whose Sum is 0 - 二分
希望自己以后能够及时做总结,不要拖,思路和逻辑真的很重要,其次是个人的代码实现能力,还有很长的路要走。
这一题卡了两天,归根到底几种写法的思想是一样的,但也总算写通了。
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 2 28 ) 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).
解题思路:先分别求出前两列后两列的所有的和,
然后对后两列的和进行排序,
再对后两列求完和的每个元素在前两列的和进行二分查找看有 多少种情况。
需要注意一下二分的两个点:1.二分只能对于有序数组进行查找,所以需要先排序
2.二分模版只能找到所需要找到的特定元素,无法找到有多少个该元素,在这点上需要处理一下
(原来二分不会超时。。。时间复杂度只有O(log2n)。)
法一:
直接用库函数过。
(原来调用函数内存会是手动模拟的两倍多)
//-45 22 -23 -72 -19 -14 48 -10 42 -16 26 -121
//-41 -27 -68 8 12 -63 -1 -59 56 30 86 26
//-36 53 17 -15 -11 -6 79 21 -37 77 40 39
//-36 30 -6 -83 -79 -74 56 -2 -75 -46 -121 40
//26 -38 -12 -99 -95 -90 -28 -70 -10 62 52 52
//-32 -54 -86 -6 45 39 86
//解题思路:先分别求出前两列后两列的所有的和,
//然后对后两列的和进行排序,
//再对后两列进行二分查找看有多少种情况
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define inf 0x3f3f3f3f
int a[5000],b[5000],c[5000],d[5000];
int p[16000010],q[16000010];
int n,k;
int main()
{
while(~scanf("%d",&n))
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
memset(q,0,sizeof(q));
int i,j;
int ans=0;
for(i=0; i<n; i++)
{
scanf("%d %d %d %d",&a[i],&b[i],&c[i],&d[i]);
}
k=0;
for(i=0; i<n; i++) //看一下时间复杂度
{
for(j=0; j<n; j++)
{
p[k]=a[i]+b[j];
q[k]=c[i]+d[j];
k++;
}
}
sort(p,p+k);
int sum=0;
for(i=0;i<k;i++)
{
sum+=upper_bound(p,p+k,-q[i])-lower_bound(p,p+k,-q[i]);
}
printf("%d\n",sum);
}
return 0;
}
法二:这样写会超时
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define inf 0x3f3f3f3f
int a[5000],b[5000],c[5000],d[5000];
int p[16000010],q[16000010];
int n,k;
int main()
{
while(~scanf("%d",&n))
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
int i,j,r;
int ans=0;
for(i=1; i<=n; i++)
{
scanf("%d %d %d %d",&a[i],&b[i],&c[i],&d[i]);
}
k=0;
for(i=1; i<=n; i++) //看一下时间复杂度16000000 一亿多,一秒多,1.5s
{
for(j=1; j<=n; j++)
{
p[k]=a[i]+b[j];
q[k]=c[i]+d[j];
k++;
}
}
sort(p,p+k);
int u,v;
for(j=0; j<k; j++)
{
q[j]=-q[j];
for(i=0; i<k; i++)
{
if(p[i]>q[j])
{
v=i;
break;
}
else
continue;
}
if(i==k)
v=k;
for(r=0; r<k; r++)
{
if(p[r]>=q[j])
{
u=r;
break;
}
else
continue;
}
if(r==k)
u=k;
ans+=v-u;
}
printf("%d\n",ans);
}
return 0;
}
法三:本来以为二分无法用,因为思维太局限了,认为二分只能找到特定的元素,无法计算出现的个数。但真的是人外有人,在这里借鉴了别人的思想,利用二分找到特定的元素之后,记录下标,再在该元素的前后寻找相同的元素计算个数即可。
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define inf 0x3f3f3f3f
int a[5000],b[5000],c[5000],d[5000];
int p[16000010],q[16000010];
long long ans;
int n,k;
void erfen(int x)//因为p定义的是全局变量,所以不用再传入了
{
int l=0,r=k-1;
int mid;
while(l<=r)
{
mid=(l+r)/2;
if(x==p[mid])
break;
if(x>p[mid])
{
l=mid+1;
}
if(x<p[mid])
{
r=mid-1;
}
}
if(x==p[mid])
{
int w=mid+1;//不能放在下面,因为mid经过下面的操作会改变
while(p[mid]==x)
{
if(mid<0)
break;
mid--;
ans++;
}
while(p[w]==x)
{
if(w+1>k)
break;
w++;
ans++;
}
}//再找到特定元素之后,再在该元素的前后寻找相同的元素,这一点的处理值得学习
}
int main()
{
while(~scanf("%d",&n))
{
ans=0;
int i,j;
for(i=1; i<=n; i++)
{
scanf("%d %d %d %d",&a[i],&b[i],&c[i],&d[i]);
}
k=0;
for(i=1; i<=n; i++)
{
for(j=1; j<=n; j++)
{
p[k]=a[i]+b[j];
q[k]=c[i]+d[j];
k++;
}
}
sort(p,p+k);
for(i=0; i<k; i++)
erfen(-q[i]);
printf("%lld\n",ans);
}
return 0;
}
法四:这个也算是手动模拟了第一个库函数的源代码了,内存比调用函数小两倍多。思路:利用二分找到两个下标,一个是大于等于需要查找的元素的下标,一个是大于的下标,两者之差就是出现的次数。需要注意的是:1.不能找大于和小于的,这个之前纠结了好久,因为如果不存在任何一种情况,会出现0-0的情况,假设一个0是大于的下标,小于的没有找到,那么也会默认为0,而下标为0所对应的元素是大于的,并不是我们所说的小于的。所以应该找大于等于和大于等于的,在集合中,这一部分是存在交集的,而大于和小于并不存在交集;2.考虑到找不到的情况,返回一个-1,不能返回为0,对于编译器会引起歧义,不知道是因为没有找到所以return 0还是返回的下标是0。返回-1之后还需要再进行判断,突然好佩服我自己哈哈哈哈。终于写出来了。
//解题思路:先分别求出前两列后两列的所有的和,
//然后对后两列的和进行排序,
//再对后两列进行二分查找看有多少种情况
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define inf 0x3f3f3f3f
int a[5000],b[5000],c[5000],d[5000];
int p[16000010],q[16000010];
int ans;
int n,k;
int erfenz(int x)//因为p是全局变量,所以不用传入两个
{
int l=0,r=k-1;
int mid;
while(l<=r)
{
mid=(l+r)/2;
if(x==p[mid])
break;
// return p[mid];
if(x>p[mid])
{
l=mid+1;
}
if(x<p[mid])
{
r=mid-1;
}
}//这道题是要求
//一开始返回0,return0不能这样写,因为要是第一列全为0的话,返回也是0,不能确定是否找到;
if(x==p[mid])
{
while(p[mid]==x)
{
if(mid<0)
break;
mid--;
ans++;
}
return mid+1;
}
//如果没有找到
return -1;
}
int erfeny(int x)//因为p是全局变量,所以不用传入两个
{
int l=0,r=k-1;
int mid;
while(l<=r)
{
mid=(l+r)/2;
if(x==p[mid])
break;
// return p[mid];
if(x>p[mid])
{
l=mid+1;
}
if(x<p[mid])
{
r=mid-1;
}
}
//一开始返回0,return0不能这样写,因为要是第一列全为0的话,返回也是0,不能确定是否找到;
if(x==p[mid])
{
int w=mid+1;
while(p[w]==x)
{
if(w+1>k)
break;
w++;
ans++;
}
return w;
}
return -1;
}
int main()
{
while(~scanf("%d",&n))
{
ans=0;
int i,j;
for(i=1; i<=n; i++)
{
scanf("%d %d %d %d",&a[i],&b[i],&c[i],&d[i]);
}
k=0;
for(i=1; i<=n; i++)
{
for(j=1; j<=n; j++)
{
p[k]=a[i]+b[j];
q[k]=c[i]+d[j];
k++;
}
}
sort(p,p+k);
// for(i=0; i<k; i++)
// erfen(-q[i]);
int sum=0;
int l,f;
//for(j=0; j<k; j++)
//{
int v=0,u=0;
for(i=0; i<k; i++)
{
v=erfenz(-q[i]);
u=erfeny(-q[i]);
if(u==-1&&v==-1)
{
// u-v=0;
sum+=0;
}
if(u==-1&&v!=-1)
{
// u-v=0;
for(l=v+1;l<k;l++)
{
if(p[l]!=p[v])
{
f=l;
}
}
// u-v=f-v;
int ff=f-v;
sum+=ff;// 或者直接用最后一个下标减去当前下标
}
// if(u!=-1&&v==-1)//不存在
else
sum+=u-v;
}
// if(i==k)
// {
// v=k;
// }
// for(i=0; i<k; i++)
// {
//
// }
// if(i==k)
// {
// u=k;
// }
// }
printf("%d\n",sum);
}
return 0;
}