雜湊表入門題目總結(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;
}