1. 程式人生 > >雜湊表入門題目總結(HDU 1280、1425、2027、3833、1496、2648 POJ 1200)

雜湊表入門題目總結(HDU 1280、1425、2027、3833、1496、2648 POJ 1200)

今天做了一天的雜湊表題目,雖然不是雜湊演算法,但是感覺先把這雜湊表搞定了,再學雜湊演算法的時候會快一些,所以今天搞得現在都有點頭痛了,看了一天的電腦……可憐

快要睡覺了,整理一下今天的成果。整理一下題目與思想,明天學雜湊演算法的時候應該會有些幫助。

先推薦一個雜湊入門網站,還沒做題之前,我把這網站關於雜湊的都看了個遍,有些明白了才開始做題的:http://wenku.baidu.com/view/98383d34f111f18583d05a81.html

總的來說,在很多資料面前,如果要處理,我們自然會想到用陣列,連結串列,或者線段樹去處理,當然,這些不失為一種好的解決辦法。不過陣列定址容易,插入和刪除困難,而且如果n太大,遍歷會超時;連結串列插入刪除容易,但是遍歷很慢;線段樹結構複雜,在很多陣列面前,處理和遍歷也很煩,有時也是會超時的。所以人們就想到了這個雜湊演算法解決這個問題。

由上面的解釋大概也明白了雜湊是怎麼做出來的了。就是把那些資料給上地址,結合連結串列的特點,就能做到定址又快,插入刪除查詢又快的方法了。不過這裡說太多了,這應該是雜湊演算法的時候才會用得到的。雜湊表幾乎都是和地址有關的,把地址給搞定了,題目就做得很簡單了,時間也會少很少了。

其實雜湊就像字典一樣,把那些地址都給確定好了,然後查詢的時候就快了。看上面給出的那個網站,裡面有更詳細的解釋……

做雜湊的原因:這段時間做題發現自己對資料處理很弱,在資料很多的情況下,總是會超時……而雜湊在這方面出類拔萃,不得不學了!

第一道練習題:HDU 1280 前m大的數

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
#define M 1000005  
typedef long long ll;  
using namespace std;  
int a[3005],b[10005];  
int main()  
{  
    int n,m,i,j;  
    while(cin>>n>>m)  
    {  
        mem(b,0);  
        int k=0;  
        for(i=0; i<n; i++)  
            cin>>a[i];  
        for(i=0; i<n; i++)  
            for(j=i+1; j<n; j++)  
                b[a[i]+a[j]]++;  //直接把相加的數當作地址,也就是下標
        for(i=10000; i>0&&m>0;)  
        {  
            if(!b[i]) {i--;continue;}  
            if(k) cout<<' '<<i;    //因為空格沒處理PE了一發  
            else cout<<i;  
            k=1;  
            b[i]--;   //相加可能有相同的,而輸出可以有重複的,和北航校賽那題太像了!  
            m--;  
        }  
        cout<<endl;  
    }  
    return 0;  
} 

第二題 HDU 1425 sort

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
typedef long long ll;  
using namespace std;  
#define M 500000  
int hash[M*2+1];  
int main()  
{  
    int n,m;  
    while(cin>>n>>m)  
    {  
        int a,i,j,k=0;  
        mem(b,0);  
        for(i=0; i<n; i++)  
        {  
            scanf("%d",&a);  
            hash[M+a]=1;    //和上面那題差不多,+M的原因就是防止下標為負的情況
        }  
        for(i=M*2; i>=0&&m>0; i--)  
        {  
            if(!hash[i]) continue;  
            if(k) cout<<' '<<i-M;  
            else cout<<i-M;  
            k=1;  
            m--;  
        }  
        cout<<endl;  
    }  
    return 0;  
} 

第三題:HDU 2027 統計母音

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
typedef long long ll;  
using namespace std;  
#define M 100005  
char s[101],hash[6]={'a','e','i','o','u'};  
int main()  
{  
    int t;  
    cin>>t;  
    getchar();  
    while(t--)  
    {  
        int i,a[200]={0};  
        gets(s);  
        for(i=0;i<strlen(s);i++)  
            a[s[i]]++;  
        for(i=0;i<5;i++)  
            printf("%c:%d\n",hash[i],a[hash[i]]);  //母音字母本身作為下標直接查詢
        if(t) puts("");  
    }  
    return 0;  
} 

思路:這題實在是苦死我了,剛開始看題目不知道怎麼意思。有點讓人產生歧義。又看了別人的解釋,自己還是沒明白什麼意思,看得自己都困了睡了一下起來……發現就是給出一組數,如果有有 a-b=b-c 的話就是Y,否則是N。當然a 的位置在b 的前面,c 的位置在b 的後面。這樣就明白了。唉……讀題好慢……題目給出那個permutation,讓自己產生歧義理解了好久。

不過這題當然用雜湊就快了,如果用平常的方法的話,那就是3個for迴圈才能搞定了,雖然能搞定,但是n<=10000,這也會超時……因為題目的重要的字眼是位置,他們的位置是a,b,c 。而雜湊的優勢正好在於處理位置地址方面無與倫比,那這題用雜湊做的話,當然是最好的解法。那要具體怎麼做呢??……

比如一組數列:6 3 2 5 4 1 (題目給出的數是不會重複的)設輸入的數為s。

輸入的數直接做為hash陣列的下標,陣列等於1,表明這個數出現過。即hash[6]=1,然後由題目的條件,即6為b,則要找出a在不在前面出現過,如果出現過的話,那這個數列的後面肯定還有一個數c 符合a-b=b-c,轉換一下就是2*b=a+c。就是小學的這個公式。現在問題來了,要怎麼查詢a最快呢,能不能直接一個判斷就可以了呢??雜湊說當然可以……

if(hash[s-j]+hash[s+j]==1)   就有了這個式子。例如輸入6的時候判斷一下j=1->s-1(j從1到s-1)。如果s-j出現過,那麼s+j也會在s後面出現的,就是Y了;如果s+j出現過,那麼s-j也會在s後面出現的,也是Y了;如果都沒有出現過,那j 自增再判斷……

比如當輸入s=2 時,j=1->s-1,j=1時,s+j==3出現過,且s-j==1沒出現過,這就正好符合了。2*2==3+1 正好是題目給出的式子的條件。s就是其中位數而已,如此判斷豈不快唉……

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
typedef long long ll;  
using namespace std;  
#define M 100005  
int hash[10003];  
int main()  
{  
    int t;  
    scanf("%d",&t);  
    while (t--)  
    {  
        int n,flag=0,i,j,s;  
        mem(hash,0);  
        scanf("%d",&n);  
        for (i = 1; i <= n; i++)  
        {  
            scanf("%d",&s);  
            hash[s]=1;  
            if (flag == 0)  
            {  
                for(j=1;j<a&&j+s<=n;j++)     //b=s  
                {  
                    if(hash[s-j]+hash[s+j]==1)   //判斷a或者c是否出現過,只能出現一個才符合,比如:3 1 2時,2前面都現在了3 和1  ,這不符合  
                    {  
                        flag=1;  
                        break;  
                    }  
                }  
            }  
        }  
        if(flag) printf("Y\n");  
        else printf("N\n");  
    }  
    return 0;  
}  

第五題:HDU 1496 Equations

題意:就是給出a,b,c,d然後求出x1,x2,x3,x4有多少組解……

思路:這題還是收穫很大啊……以前真不敢這麼想,也不敢這麼用過陣列……雜湊的思想太猛了!!

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
typedef long long ll;  
using namespace std;  
#define M 2000000  
int s[101],hash[M+3];  
int main()  
{  
    int a,b,c,d,i,j,sum;  
    for(i=1;i<101;i++)  
        s[i]=i*i;  
    while(cin>>a>>b>>c>>d)  
    {  
        if(a>0&&b>0&&c>0&&d>0||a<0&&b<0&&c<0&&d<0)  //因為x那項永遠是正數,如果係數都為正或者為負的時候明顯不等於0  
        {  
            printf("0\n");  
            continue;  
        }  
        mem(hash,0);  
        sum=0;  
        for(i=1;i<101;i++)  
            for(j=1;j<101;j++)                //也可以先求前1項或者前3的,但是3個迴圈肯定比2個的時間多,所以這就是為什麼先求前兩項的原因  
                hash[a*s[i]+b*s[j]+M/2]++;    //(加上M/2的原因是防止下標為負,先求前兩項,例如a*s[i]+b*s[j] == 5     
        for(i=1;i<101;i++)  
            for(j=1;j<101;j++)  
                sum+=hash[-(c*s[i]+d*s[j])+M/2];   //與前面例如對應:如果c*s[i]+d*s[j] == -5  那麼相加正好等於0,符合題解,正好求出x,故項數在前面加號就是5啦,5那項有多少,-5與之對應的就有多少解……  
        printf("%d\n",sum*16);   //因為 x 是平方,所以正負數的平方都一樣,一個x就會有兩個解了,4個x當然就有2^4=16個解
    }  
    return 0;  
}

第六題:HDU 2648 shopping

這題剛開始確實不會,研究了好久,還是不會。迫不得已看了下別人的做法,實在高明啊!!

又是一道雜湊實質題。地址雜湊,唉,這思想也不知道哪個大神想出來的,佩服啊……見識了奮鬥

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<set>  
#include<map>  
#include<queue>  
#include<vector>  
#include<stack>  
#include<ctime>  
#include<cstdlib>  
#define mem(a,b) memset(a,b,sizeof(a))  
typedef long long ll;  
using namespace std;  
#define M 2000000  
int hash[10003];  
int main()  
{  
    int n,m,i,j,k,w,a;  
    string s;  
    while(scanf("%d",&n)!=EOF)  
    {  
        map<string,int>q;  //拿map中字串對映地址,就轉換成了整形了,雜湊只要能轉成整形的怎麼都好講了  
        mem(hash,0);  
        for(i=0; i<n; i++)  
        {  
            cin>>s;  
            q[s]=i;   //字串對應地址,地址從0開始編號  
            if(s=="memory") w=i;   //記住目標字串的地址  
        }  
        scanf("%d",&m);  
        for(i=0;i<m;i++)  
        {  
            int sum=0;  
            for(j=0; j<n; j++)  
            {  
                cin>>a>>s;  
                hash[q[s]]+=a;   //雜湊陣列加權值,地址相當於雜湊陣列下標,簡單明瞭  
            }  
            for(k=0; k<n; k++)  
                if(hash[w]<hash[k]) sum++;  //然後打擂臺法找出多少個就行了  
            printf("%d\n",sum+1);  
        }  
    }  
    return 0;  
}

第七題 POJ 1200

這題和上面的一樣,也是雜湊表……只要找到這個串裡能唯一表示地址的那個計算式就行了……

#include <bitset>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <utility>
#include <deque>
#include <vector>
#include <list>
#include <queue>
#include <string>
#include <complex>
#include <cstring>
#include <map>
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
#define M 16000000
char s[M];
int a[100],hash[M];
int ;
int main()
{
    int N,NC,num=0,len,i,j,ans=0,sum=0;
    scanf("%d%d",&N,&NC);
    scanf("%s",&s);
    len=strlen(s);
    for(i=0; i<len; i++)
        if(!a[s[i]]) a[s[i]]=++num;  //其實不從0開始也行的,只不過從0開始,後面計算的時候hash陣列的下標就比較小了,這樣不會越記憶體了
    for(i=0; i<len-N+1; i++)
    {
        sum=0;
        for(j=i; j<i+N; j++)
            sum+=sum*NC+a[s[j]];   //也可以不乘以NC,但是一定要乘以一個>=NC的數,因為這樣才能唯一的表示一個字串轉換成整形陣列的下標地址,而NC最小嘛
        if(!hash[sum])
        {
            ans++;
            hash[sum]=1;
        }
    }
    printf("%d\n",ans);
    return 0;
}