浏览器标题切换
浏览器标题切换end

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;
}

 

posted @ 2019-05-26 12:51  抓水母的派大星  阅读(107)  评论(0编辑  收藏  举报