今天做了一天的哈希表题目,虽然不是哈希算法,但是感觉先把这哈希表搞定了,再学哈希算法的时候会快一些,所以今天搞得现在都有点头痛了,看了一天的电脑……
快要睡觉了,整理一下今天的成果。整理一下题目与思想,明天学哈希算法的时候应该会有些帮助。
先推荐一个哈希入门网站,还没做题之前,我把这网站关于哈希的都看了个遍,有些明白了才开始做题的: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; }
第四题:HDU 3833 YY's new problem
思路:这题实在是苦死我了,刚开始看题目不知道怎么意思。有点让人产生歧义。又看了别人的解释,自己还是没明白什么意思,看得自己都困了睡了一下起来……发现就是给出一组数,如果有有 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; }
题意:就是给出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; }