今天做了一天的哈希表题目,虽然不是哈希算法,但是感觉先把这哈希表搞定了,再学哈希算法的时候会快一些,所以今天搞得现在都有点头痛了,看了一天的电脑……
快要睡觉了,整理一下今天的成果。整理一下题目与思想,明天学哈希算法的时候应该会有些帮助。
先推荐一个哈希入门网站,还没做题之前,我把这网站关于哈希的都看了个遍,有些明白了才开始做题的: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;
}
第五题: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;
}