【高效算法设计-中途相遇法】4 Values whose Sum is 0 哈希表

来源:互联网 发布:.net java 区别 编辑:程序博客网 时间:2024/05/06 01:49
4 Values whose Sum is 0
Time Limit: 15000MS Memory Limit: 228000KTotal Submissions: 16375 Accepted: 4748Case Time Limit: 5000MS

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 -4626 -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).

Source

Southwestern Europe 2005


题意:本题题意很简单,给定4组数,每组有n个数,从每组数中各取一个数,问有多少种取法使得取出的四个数之和为0

思路:很容易想到用暴力枚举的方法,时间复杂度为O(n^4),必然超时,而对于这种从集合中选组合类的问题,我们通常采用一种方法,即中途相遇法

中途相遇法:就是从两个方向向中间靠拢的一种做法,如双向BFS,对于此题,我们可以把前两组的组合和后两组的组合分开计算,然后再看是否之和为0,即我们先计算前两组数的和,利用数据结构存储起来,再计算后两组数的和,进行查询,这样时间复杂度就降低到O(n^2*查询的时间复杂度),由于本题数据范围较大,无法使用vis数组进行O(1)的查询,因此只能考虑使用map或者hash表

由于map的代码复杂度较低,时间复杂度为O(n^2*logn),我们可以先尝试用map写一个demo程序
使用map的代码如下

#include<cstdio>#include<cstring>#include<map>using namespace std;map<int,int>MAP;int a[5][5000];int main(){    int n,i,j,tt,ans;    while(~scanf("%d",&n))    {        ans=0;        MAP.clear();        for(j=1;j<=n;j++)            for(i=1;i<=4;i++)                scanf("%d",&a[i][j]);        for(i=1;i<=n;i++)            for(j=1;j<=n;j++)            {                tt=a[1][i]+a[2][j];                MAP[tt]++;            }         for(i=1;i<=n;i++)            for(j=1;j<=n;j++)            {                tt=a[3][i]+a[4][j];                ans+=MAP[-tt];            }            printf("%d\n",ans);    }    return 0;}

很遗憾,使用map会超时,于是我们考虑使用hash,实现hash的方法主要使用开放寻址法或者哈希邻接表
利用head[]数组和next[]数组可以轻松完成哈希邻接表,我们使用除法取余作为哈希函数,head数组的大小为素数mod的大小,这里素数取得越大,发生碰撞的可能性就越小,next[]的大小为哈希表中一共需要存入数据个数的大小,即前两组数的组合个数,n^2

哈希AC代码如下:
#include<cstdio>#include<cstring>#include<cmath>using namespace std;//const int mod=3999971;const int mod=10000007;int head[mod+50];int next[16000000+50];int a[5][5000];int res[16000000+50];void insert_hash(int x){    int v=abs(res[x]%mod);    next[x]=head[v];    head[v]=x;}int find_hash(int x){    int cnt=0;    int t=abs(x);    int u=head[t%mod];    while(u!=-1)    {        if(res[u]==x)            cnt++;        u=next[u];    }    return cnt;}int main(){    int n,i,j,tt,ans,k;    while(~scanf("%d",&n))    {        ans=0;        memset(head,-1,sizeof head);        for(j=1;j<=n;j++)            for(i=1;i<=4;i++)                scanf("%d",&a[i][j]);        k=0;        for(i=1;i<=n;i++)            for(j=1;j<=n;j++)            {                tt=a[1][i]+a[2][j];                res[++k]=tt;                insert_hash(k);            }         for(i=1;i<=n;i++)            for(j=1;j<=n;j++)            {                tt=a[3][i]+a[4][j];                ans+=find_hash(-tt);            }            printf("%d\n",ans);    }    return 0;}



0 0