总算到了写Hash这个专题的时候,以前一直都搞不清Hash到底有什么用,
现在才发现Hash在各种领域包括ACM的重要性,判重是灰常灰常重要的。
下面应该会总结
整数的hash(线性的, 康托展开)
字符串Hash( ELFHash, 多项式Hash, BKRDHash)
Hash的灵活应用
Hash函数运用很广,自己见识浅薄,目前来看最大的用途就是判重了。
自己简单总结一下:(其实我是参考黑书的) 哈希表的思路是根据关键码值直接访问。也就是说,这是一个把关键码值映射到表中的一个位置来访问记录的过程。
这个映射函数叫做hash函数,用h来表示,存放记录的数组叫做哈哈希表。表里的每一个位置叫做一个槽,槽的数目用M表示。 哈希表的插入和查找算法 (1)计算函数值h(K) (2)从槽h(K)开始,使用(如果需要的话)冲突解决策略定位包括关键码K的记录 (3)如果需要插入,在槽里把数据插入即可。 冲突解决办法 开散列(拉链法) 闭散列(开放寻址法)
1. HDU 1425 sort
题意:给定一个序列,从大到小输出前m大的数,可以用sort排序之后水过。
分析:这里是我学习的第一个Hash思想,
就是最简单的完全散列,将数组下标和key对应起来
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define M 1000005 bool Hash[M]; int m,n,a; int main() { while(~scanf("%d%d",&n,&m)) { for(int i=0;i<n;i++) { scanf("%d",&a); Hash[a+500000] = true; } for(int i=1000000;m>0;i--) { if(Hash[i]) { if(m!=1) printf("%d ",i-500000); else printf("%d\n",i-500000); m--; } } } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int x[1000000+10]; int n,k; void quicksort(int b,int e,int a[]) { int i = b, j = e, x = a[(b+e)>>1]; do { while(a[i] > x) i++; while(a[j] < x) j--; if(i <= j) swap(a[i++],a[j--]); }while(i < j); if(j < e) quicksort(i,e,a); if(j > b) quicksort(b,j,a); } int main() { while(~scanf("%d%d",&n,&k)) { for(int i=0;i<n;i++) scanf("%d",&x[i]); quicksort(0,n-1,x); for(int i=0;i<k-1;i++) printf("%d ",x[i]); printf("%d\n",x[k-1]); } return 0; }
2. HDU 1800 Flying to Mars
题意:N个soldiers,每个soldier有一个等级,高等级的soldier可以教低等级的soldier,反之不能,教学需要用一个broomsticks,
等级依次递减的soldiers可以共用一个broomstick,问最少需要多少个。
分析:很容易想到是出现次数最高的等级,然后直接输出出现次数。
题解:题目数据很弱,但是正规的作法就是要用字符串Hash,还需要考虑前置0的问题
字符串Hash,这里我学习了 (累乘 seed)(ELFHash函数) 两种
int HashFunc() { int Hash = 0; int i = 0; while(s[i] == '0') i++; for(;s[i]!='\0';i++) { Hash += ((Hash * seed[s[i]&1] + s[i]) % MOD); } return Hash; }
unsigned int ELFHash(char* str) { unsigned int Hash = 0; unsigned int x = 0; while(*str) { Hash = (Hash << 4) + (*str++); //hash左移4位,把当前字符ascii存入hash第四位 if((x == Hash & 0xF0000000L) != 0 ) {//如果最高位的思位不为0,则说明字符多余7个,现在正在存第8个字符,如果不处理,再加入下一个字符时,第一个字符会被移出。 Hash ^= (x >> 24);//因为1-4位刚刚存储了新加入到字符,所以不能>>28 Hash &= ~x; //上面这行代码并不会对X有影响,本身X和hash的高4位相同,下面这行代码&~即对28-31(高4位)位清零。 } } //返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负) return (Hash & 0x7FFFFFFF)
#include <cstdio> #include <cstring> #include <algorithm> #define CLR(a,b) memset(a,b,sizeof(a)) #define M 1000007 using namespace std; int n,HashTable[M]; char str[33]; unsigned int ELFHash(char* str) { unsigned int Hash = 0; unsigned int x = 0; while(*str) { Hash = (Hash << 4) + (*str++); if((x == Hash & 0xF0000000L) != 0 ) { Hash ^= (x >> 24); Hash &= ~x; } } return (Hash & 0x7FFFFFFF); } int solve(char* str) { CLR(HashTable,0); int ret = 0; for(int i=0;i<n;i++) { scanf("%s",str); while(*str == '0') str++; int Hash = ELFHash(str)%M; HashTable[Hash]++; ret = max(HashTable[Hash],ret); } return ret; } int main() { while(~scanf("%d",&n)) { printf("%d\n",solve(str)); } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MOD 10000007 #define MAXN 3003 #define LEN 31 const int seed[2] = {13,37}; int level[MAXN], n ; char s[LEN]; int HashFunc() { int Hash = 0; int i = 0; while(s[i] == '0') i++; for(;s[i]!='\0';i++) { Hash += ((Hash * seed[s[i]&1] + s[i]) % MOD); } return Hash; } int main() { while(~scanf("%d",&n)) { for(int i=0;i<n;i++) { scanf("%s",s); level[i] = HashFunc(); } sort(level,level+n); int res = 0; for(int i=0;i<n;) { int j = i+1; while(j<n && level[j] == level[i]) j++; if(res < j - i) res = j - i; i = j; } printf("%d\n",res); } return 0; }
// ELF Hash Function unsigned int ELFHash(char *str) { unsigned int hash = 0; unsigned int x = 0; while (*str) { hash = (hash << 4) + (*str++);//hash左移4位,把当前字符ASCII存入hash低四位。 if ((x = hash & 0xF0000000L) != 0) { //如果最高的四位不为0,则说明字符多余7个,现在正在存第8个字符,如果不处理,再加下一个字符时,第一个字符会被移出,因此要有如下处理。 //该处理,如果对于字符串(a-z 或者A-Z)就会仅仅影响5-8位,否则会影响5-31位,因为C语言使用的算数移位 //因为1-4位刚刚存储了新加入到字符,所以不能>>28 hash ^= (x >> 24); //上面这行代码并不会对X有影响,本身X和hash的高4位相同,下面这行代码&~即对28-31(高4位)位清零。 hash &= ~x; } } //返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负) return (hash & 0x7FFFFFFF); }
#include <cstdio> #include <cstring> #include <algorithm> #define CLR(a,b) memset(a,b,sizeof(a)) #define M 1000007 using namespace std; int n,HashTable[M]; char str[33]; unsigned int ELFHash(char* str) { unsigned int Hash = 0; unsigned int x = 0; while(*str) { Hash = (Hash << 4) + (*str++); if((x = Hash & 0xF0000000L) != 0 ) { Hash ^= (x >> 24); Hash &= ~x; } } return (Hash & 0x7FFFFFFF); } int solve(char* str) { CLR(HashTable,0); int ret = 0; for(int i=0;i<n;i++) { scanf("%s",str); while(*str == '0') str++; int Hash = ELFHash(str)%M; HashTable[Hash]++; ret = max(HashTable[Hash],ret); } return ret; } int main() { while(~scanf("%d",&n)) { printf("%d\n",solve(str)); } return 0; }
3. HDU 1496 Equations
题意:给定a,b,c,d四个数,问使得 式子 a*x1^2+b*x2^2+c*x3^2+d*x4^2=0 成立的条件x1,x2,x3,x4 的组合有多少种。
题目限定( x1,x2,x3,x4 ) that verifies the equation, xi is an integer from [-100,100]
分析:这题也是用到的Hash的思想,我们可以这样来做,把式子分为两边,将一部分移到右边去
枚举左边式子,存入Hash表当中,然后再枚举右边的式子,看是否和左边相等,如果是的话,res++
因为这里的xi 是 [-100,100], 所以有正负两种情况,2*2*2*2 = 16,最后记得*16。还有就是要一个优化,判断是a,b,c,d是否同号
这个枚举属于完全散列,没有冲突,数组开200w可以接受(枚举+完全散列)
#include <cstdio> #include <cstring> #define CLR(a,b) memset(a,b,sizeof(a)) int Hash[2000020]; int a,b,c,d,cnt; int main() { while(~scanf("%d %d %d %d",&a,&b,&c,&d)) { if(a*b>0 && b*c>0 && c*d>0) { printf("0\n"); continue; } cnt = 0; CLR(Hash,0); for(int i=1;i<=100;i++) for(int j=1;j<=100;j++) Hash[a*i*i+b*j*j+1000000]++; for(int i=1;i<=100;i++) for(int j=1;j<=100;j++) cnt += Hash[-c*i*i-d*j*j+1000000]; printf("%d\n",cnt*16); } return 0; }
4. HDU 4334 Trouble
题意:给定5个集合,从每个集合中取出一个数分别为a1,a2,a3,a4,a5,问是否能够有a1+a2+a3+a4+a5 = 0
分析:这题和1496差不多,五个集合,分成两个 和 三个 来考虑。
这个枚举用了线性探测解决冲突问题(枚举+散列+线性探测解决冲突)
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef __int64 ll; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 300007 ll S[5][220]; ll Hash[M]; bool flag[M]; int T,N; int getHash(ll k) { ll key = k%M; if(key < 0) key += M; while(flag[key] && Hash[key] != k) //解决冲突 { key = (key + 1) %M; } return key; } int main() { scanf("%d",&T); while(T--) { CLR(flag,false); scanf("%d",&N); for(int i=0;i<5;i++) for(int j=0;j<N;j++) scanf("%I64d",&S[i][j]); for(int i=0;i<N;i++) for(int j=0;j<N;j++) { int key = getHash(S[0][i] + S[1][j]); Hash[key] = S[0][i] + S[1][j]; flag[key] = true; } for(int i=0;i<N;i++) for(int j=0;j<N;j++) for(int k=0;k<N;k++) { int key = getHash(-S[2][i] - S[3][j] - S[4][k]); if(flag[key]) { puts("Yes"); goto AC; } } puts("No"); AC:; } return 0; }
http://blog.csdn.net/qq564690377/article/details/7824691
5. HDU 1043 Eight
题意:不需要再解释,传说中不做人生会不完整的八数码问题。
分析:八数码已经达到了10重境界。我无法分析
题解:这题我主要是学习康托展开Hash函数的。
后续:后续如果有时间或者能力的话要研究八数码的多重境界才行啊
康托展开是一种特殊的哈希函数 X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。 例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884. 解释: 排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8! 排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7! 以此类推,直至0*0! 用途 显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。 康托展开的逆运算 既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。 如n=5,x=96时: 首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!) 用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4. 用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5. 用5去除2!得到2余1,类似地,这一位是3. 用1去除1!得到1余0,这一位是2. 最后一位只能是1. 所以这个数是45321. 按以上方法可以得出通用的算法。
int Cantor(int* s,int len) { int i, j, ret; for(i=0;i<len;i++) { for(j=i+1;j<len;j++) { int cnt = 0; if(s[i] > s[j]) cnt++; } ret += cnt * fac[len-i-1]; } return ret; }
#include <cstdio> #include <cstring> #include <algorithm> #include <string> #include <queue> #include <iostream> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAXN 366000 //9! = 362880总共状态数 int fac[] = {1,1,2,6,24,120,720,5040,40320,362880}; bool vis[MAXN]; string path[MAXN]; //4个方向 int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; //路劲需要放过来存放,原来对应的是udlr char cdir[] = "durl"; int ok = 0; struct Node { int s[9]; int loc; int Hash; string path; }; int Cantor(int* s) { int ret = 0; for(int i=0;i<9;i++) { int cnt = 0; for(int j=i+1;j<9;j++) if(s[j] < s[i]) cnt++; ret += cnt * fac[9-i-1]; //康托展开公式 } return ret; } void BFS() { CLR(vis,false); Node cur, next; for(int i=0;i<9;i++) cur.s[i] = i+1; cur.loc = 8; cur.Hash = ok; cur.path = ""; queue<Node> que; que.push(cur); path[ok] = ""; while(!que.empty()) { cur = que.front(); que.pop(); int x = cur.loc/3; int y = cur.loc%3; for(int i=0;i<4;i++) { int xx = x + dir[i][0]; int yy = y + dir[i][1]; if(xx < 0 || xx > 2 || yy < 0 || yy > 2) continue; next = cur; next.loc = xx*3 + yy; next.s[cur.loc] = next.s[next.loc]; next.s[next.loc] = 9; next.Hash = Cantor(next.s); if(!vis[next.Hash]) { vis[next.Hash] = true; next.path = cdir[i] + next.path; que.push(next); path[next.Hash] = next.path; } } } } char str[100]; Node node; int main() { BFS(); //预处理 while(gets(str)) { for(int i=0,k=0;str[i];i++) { if(str[i] <= '9' && str[i] >= '0') node.s[k++] = str[i] - '0'; else if(str[i] == 'x') node.s[k++] = 9; } node.Hash = Cantor(node.s); if(node.Hash == ok) puts(""); else if(vis[node.Hash]) { cout<<path[node.Hash]<<endl; } else puts("unsolvable"); } return 0; }
6. HDU 2648 Shopping
题意:超市中,每个物品每天都会涨价,对特定的一个物品,输出每天在所有商品中的价格的rank
分析:主要是字符串Hash,需要处理冲突问题,这里学习了KID大神的代码,果然神速。。我居然进第一版了。
题解:大概思路,用的字符串Hash+vector来处理的。vector不定长数组果然还是有犀利的时候。当然,这题可以用map水过,但是速度就慢很多了。
这里用的是BKRDHash函数,确实挺好用的,写起来比ELFHash好写啊。
还有这里vector巧妙避免冲突也是相当给力啊
1 unsigned int BKDRHash(char* str) 2 { 3 unsigned int seed = 131; 4 unsigned int hash = 0; 5 while(*str) 6 { 7 hash = hash*seed + (*str++); 8 } 9 return (hash & 0x7FFFFFFF); 10 }
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 3131 struct Shop { int price; char name[33]; }shop; int n,m,ans,rank; vector<Shop> Hash[M]; unsigned int BKDRHash(char* str) { unsigned int seed = 31; unsigned int hash = 0; while(*str) { hash = hash*seed + (*str++); } return (hash & 0x7FFFFFFF) % M; } int main() { while(~scanf("%d",&n)) { for(int i=0;i<M;i++) Hash[i].clear(); for(int i=0;i<n;i++) { scanf("%s",shop.name); shop.price = 0; unsigned int t = BKDRHash(shop.name); Hash[t].push_back(shop); } scanf("%d",&m); while(m--) { for(int i=0;i<n;i++) { scanf("%d %s",&shop.price,shop.name); unsigned int t = BKDRHash(shop.name); for(int j=0;j<Hash[t].size();j++) { if(strcmp(shop.name,Hash[t][j].name) == 0) { Hash[t][j].price += shop.price; if(strcmp(shop.name,"memory") == 0) ans = Hash[t][j].price; break; } } } rank = 1; for(int i=0;i<M;i++) for(int j=0;j<Hash[i].size();j++) if(Hash[i][j].price > ans) rank++; printf("%d\n",rank); } } return 0; }
#include <iostream> #include <string> #include <map> using namespace std; string name[10001]; int n,m; int main() { while(~scanf("%d",&n)) { for(int i=0;i<n;i++) cin>>name[i]; scanf("%d",&m); map<string,int> M; map<string,int>:: iterator it; while(m--) { int price; string shop; for(int i=0;i<n;i++) { cin>>price>>shop; M[shop] += price; } int cnt = 0; for(it=M.begin();it!=M.end();it++) if(it->second > M["memory"]) cnt++; cout<<cnt+1<<endl; } } return 0; }
7. HDU 4277 USACO ORZ
题意:给出N段线段,用所有这些线段,能够组成多少种不同的三角形。
分析:dfs深搜+hash判重。。set判重也可以。注意这里还要考虑的dfs剪枝,也是判重。就是a,b,c是组合,不是排列。
题解:速度还算可以吧。500ms
这里用的是乱七八糟的方法构造了一个hashtable。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 1000007 int s[55],n,t; struct hashtable { int size,ans[M][3],next[M],h[M]; int hash(int a,int b,int c) { return (((((a*131)+b)*131+c)*131)&0x7fffffff)%M; } void insert(int a,int b,int c) { int id = hash(a,b,c); for(int i=h[id];i>=0;i=next[i]) if(ans[i][0] == a && ans[i][1] == b && ans[i][2] == c) return; ans[size][0] = a, ans[size][1] = b, ans[size][2] = c; next[size] = h[id], h[id] = size++; } void clear() { CLR(h,-1); size = 0; } }g; int ai,bi,ci; void check(int a,int b,int c) { if(a + b <= c || a + c <= b || b + c <= a) return; g.insert(a,b,c); } void dfs(int id) { if(id >= n) { if(ai <= bi && bi <= ci) check(ai,bi,ci); return; } ai += s[id]; dfs(id+1); ai -= s[id]; bi += s[id]; dfs(id+1); bi -= s[id]; ci += s[id]; dfs(id+1); ci -= s[id]; } int main() { scanf("%d",&t); while(t--) { g.clear(); scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&s[i]); ai = bi = ci = 0; dfs(0); printf("%d\n",g.size); } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 300007 int T,N,a,b,c; int x[16]; struct HashTable { int Hash[M],ans[M][3],size; int gethash() { return (((((a*37)+b)*131+c)*31)&0x7fffffff) % M; } void insert() { int key = gethash(); while(Hash[key]) { if(ans[key][0] == a && ans[key][1] == b && ans[key][2] == c) return; key++; } Hash[key] = 1; ans[key][0] = a , ans[key][1] = b , ans[key][2] = c; size++; } void init() { size = 0; CLR(Hash,0); CLR(ans,0); } }Table; void check() { if(a+b>c && a+c>b && b+c>a) { if(a >=b && b >= c) Table.insert(); } } void DFS(int step) { if(step >= N) { check(); return; } a += x[step]; DFS(step+1); a -= x[step]; b += x[step]; DFS(step+1); b -= x[step]; c += x[step]; DFS(step+1); c -= x[step]; } int main() { scanf("%d",&T); while(T--) { Table.init(); scanf("%d",&N); for(int i=0;i<N;i++) scanf("%d",&x[i]); a = b = c = 0; DFS(0); printf("%d\n",Table.size); } return 0; }
8. HDU 2523 SORT AGAIN
题意:对于一个序列,任意两个数组的组合数为ABS(x[i]-x[j]),求第K大的组合数,
分析:看清楚题意,第K大是如何定义的。
题解:简单Hash直接标记之,然后AC之,这里坑爹的是ABS里面写错,导致WA N次。
完全散列
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define ABS(a,b) a-b>0?a-b:b-a #define CLR(a,b) memset(a,b,sizeof(a)) int T,N,K,cnt,sta; int key[1010],Hash[2020]; int main() { scanf("%d",&T); while(T--) { scanf("%d %d",&N,&K); for(int i=0;i<N;i++) scanf("%d",&key[i]); CLR(Hash,0); for(int i=0;i<N;i++) for(int j=i+1;j<N;j++) Hash[ABS(key[i],key[j])]++; cnt = sta = 0; while(Hash[sta] == 0) sta++; for(;sta<=2000;sta++) { if(Hash[sta]) { cnt++; if(cnt == K) { printf("%d\n",sta); break; } } } } return 0; }
9. HDU 4648 Magic Pen
题意:给定一个序列,以及一个M,问从这个序列中找出一个连续的子序列,使得子序列之和%M为0
分析:此题有陷阱,一个是分数可能还会为负数,取模的时候一定要注意处理。第二个是O(n^2)的枚举如果没有优化会超时的
具体是代码 for(int j=n; j>i && j-i>ans; j--) //&& j-i>ans这句很关键,还有就是倒过来扫。
题解:迭代求出序列的和的mod,再枚举,区间差。
还有一个思路,做法就是维护一个前序和的模,并把这个下标放到Hash表中,然后在Hash表中找一个下标距离最远的删掉即可
依然要注意的是0的初始化问题,和shopping那题有点像。使用vector
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n,m; int sum[100010], x[100010]; int main() { while(~scanf("%d %d",&n,&m)) { sum[0] = 0; for(int i=1;i<=n;i++) { scanf("%d",&x[i]); sum[i] = sum[i-1] + x[i]; sum[i] = sum[i] % m; if(sum[i] < 0) sum[i] += m; } int ans = 0; for(int i=0;i<=n;i++) { for(int j=n; j>i && j-i>ans; j--) //&& j-i>ans这句很关键 { if((sum[j]-sum[i])%m == 0) ans = max(ans,j-i); } } printf("%d\n",ans); } return 0; }
http://blog.csdn.net/zhangyanxing666/article/details/9812335 http://blog.csdn.net/dongdongzhang_/article/details/9797905
10. HDU 4020 Ads Proposal
题意:N个customs,M个ad,给个ad有对应的custom,lens和click,总共有Q次询问,每次询问输出所有customs的排名前k位的ad的len总和
the sum of total length of the top k number of clicks for each customer.
分析:这道题做得非常不顺利,或者说是磕磕碰碰,开始用的是两个排序,第一次是一用户为关键字进行排序,第二次是在同用户中一click为关键字进行排序。
后来见到有很好的hash思想。hash[i]表示依次遍历时customer i出现的次数,也就是该用户拥有几个ad.这样的遍历是线性的。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAX 500100 int N,M,Q,U,C,L,T,cnt = 0; int name[MAX], rank[MAX], q; __int64 click[MAX], len[MAX], ans[MAX], res[MAX]; int Hash[100100]; int cmp(const void *_p,const void *_q) { int *a=(int *)_p; int *b=(int *)_q; return click[*a]>click[*b]?-1:1; } /*int cmp2(int x,int y) { return click[x] > click[y]; } */ int main() { scanf("%d",&T); while(T--) { scanf("%d %d %d",&N,&M,&Q); for(int i=0;i<M;i++) { scanf("%d %I64d %I64d",&name[i],&click[i],&len[i]); rank[i] = i; } qsort(rank,M,sizeof(rank[0]),cmp); //sort(rank,rank+M,cmp2); for(int i=1;i<=N;i++) Hash[i] = 0; for(int i=1;i<=M;i++) ans[i] = 0; for(int i=0;i<M;i++) { Hash[name[rank[i]]]++; ans[Hash[name[rank[i]]]] += len[rank[i]]; } res[0] = 0; for(int i=1;i<=M;i++) res[i] = ans[i] + res[i-1]; printf("Case #%d:\n",++cnt); for(int i=1;i<=Q;i++) { scanf("%d",&q); if(q > M) printf("%I64d\n",res[M]); else printf("%I64d\n",res[q]); } } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAX 500050 int cnt = 0; int t,n,m,q,k,i; __int64 ans[MAX]; struct Node { __int64 u,c,l,rank; bool operator<(const Node t)const { if(t.u == u) return t.c < c; return t.u > u; } }Co[MAX]; int cmp(Node x,Node y) { return x.rank < y.rank; } int main() { scanf("%d",&t); while(t--) { scanf("%d %d %d",&n,&m,&q); for(i=0;i<m;i++) scanf("%I64d %I64d %I64d",&Co[i].u,&Co[i].c,&Co[i].l); sort(Co,Co+m); // printf("\nafter once sort:\n"); // for(i=0;i<m;i++) // printf("%lld %lld %lld %lld\n",Co[i].u,Co[i].c,Co[i].l,Co[i].rank); Co[0].rank = 0; //此处改为1.错位? for(i=1;i<m;i++) { if(Co[i].u == Co[i-1].u) Co[i].rank = Co[i-1].rank+1; else Co[i].rank = 0; //此处改为1,错误? } sort(Co,Co+m,cmp); // printf("\nafter twice sort:\n"); // for(i=0;i<m;i++) // printf("%lld %lld %lld %lld\n",Co[i].u,Co[i].c,Co[i].l,Co[i].rank); ans[1] = Co[0].l; int Rank = 1; for(i=1;i<m;i++) { if(Co[i].rank == Co[i-1].rank) ans[Rank] += Co[i].l; else { ans[Rank] += ans[Rank-1]; Rank++; ans[Rank] = Co[i].l; } } ans[Rank] += ans[Rank-1]; printf("Case #%d:\n",++cnt); for(i=0;i<q;i++) { scanf("%d",&k); k = min(k,Rank); printf("%I64d\n",ans[k]); } } return 0; }
11. POJ 1200 Crazy Search
题意:给定一个字符串,由NC个不同的字符构成,问原串中长度为N的子串有多少种
分析:字符串Hash,将字符串直接对应成NC进制的N位数的一个整数,然后标记之。
题解:注意给每一个字符对应一个值,还有数组的大小是160W,注意细节。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 16000100 int N,NC,cnt,ans; char str[M]; bool Table[M]; int ascii[256]; int main() { while(~scanf("%d %d",&N,&NC)) { scanf("%s",str); CLR(ascii,0); CLR(Table,0); cnt = 1,ans = 0; int len = strlen(str); for(int i=0;cnt<=NC;i++) { if(ascii[str[i]] == 0) ascii[str[i]] = cnt++; } for(int i=0;i+N<=len;i++) { int key = 0; for(int j=i;j<i+N;j++) key = key*NC + ascii[str[j]]; if(!Table[key]) { Table[key] = 1; ans++; } } printf("%d\n",ans); } return 0; }
12. POJ 3274 Gold Balanced Lineup
题意:对于一群牛,每个牛有自己对应的十进制数,转化为二进制数的话,每一位代表一种特性,问所有特性都相等的最大连续数是多少,发现我表述不清啊。具体看题吧。就是说有一个balance range的定义要搞清楚
分析:这题充分体现了hash你牛逼之处,我使用了BKDR和ELF两种HASH,时间在250ms。
题解:这题是个好题,我搞了很久。。终于算是搞出来了。这里还要处理一个额外的数组,这个也是看别人的题解知道的。
比如,测试数据 7 3 7 6 7 2 1 4 2 我们将它转化为二进制数 7 1 1 1 6 1 1 0 7 1 1 1 2 0 1 0 1 0 0 1 4 1 0 0 2 0 1 0 再进行处理得到 1 1 1 1 2 2 2 1 3 3 3 2 4 3 4 2 5 3 4 3 6 4 4 3 7 4 5 3 如果这个时候我们再用一个数组,存放的是每一个行减去最右边那个数 0 0 0 0 1 0 0 0 2 1 1 0 3 1 1 0 4 1 2 0 5 0 1 0 6 1 1 0 7 1 2 0 可以看到第2行和第6行是一样的,这样我们可以说最大的balance range 是 4 为什么呢? 很显然,我们剪掉最右边的数,如果这个区间是满足balance range的, 那么从左到右肯定加的都是这个数,那么自然,全部减掉后,相当于没加, 那么自然如果这两行相等,说明必然是balance range, 这样,题目就转化为了求距离最远的两行的差值了。
这里关于Hash,主要是在解决冲突的问题上,如何有效得解决冲突是很重要的。。。
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 999983 #define MAX 100005 int N,K,t; int x[MAX][35]; struct HashTable { int arr[MAX][35],Hash[M],ans; /* int Encode(int id) { int i,tmp=0; for(i=0;i<K;i++) tmp=((tmp<<2)+(arr[id][i]>>4))^(arr[id][i]<<10); tmp = tmp%M; if(tmp<0) tmp=tmp+M; return tmp; } */ /* int Encode(int id) { int seed[] = {37,131}; int key = 0; for(int i=0;i<K;i++) key = key*seed[i&1] + arr[id][i]; return (key&0x7fffffff) % M; } */ /* int Encode(int id) { int seed = 131; int key = 0; for(int i=0;i<K;i++) key = key*seed + arr[id][i]; return (key&0x7fffffff) % M; } */ int Encode(int id) { int key = 0; int x = 0; for(int i=0;i<K;i++) { key = (key << 4) + arr[id][i]; if((x = key & 0xF0000000L) != 0) { key ^= (x >> 24); key &= ~x; } } return (key&0x7fffffff) % M; } void insert(int id) { int key = Encode(id); int i = 0; while(Hash[key] != -1) { for(i=0;i<K;i++) { if(arr[id][i] != arr[Hash[key]][i]) break; } if(i == K) { if(id - Hash[key] > ans) ans = id - Hash[key]; return; } key = (key+1)%M; } if(Hash[key] == -1) Hash[key] = id; // for(i=0;i<K;i++) // arr[key][i] = x[id][i]; } void clear() { ans = 0; CLR(Hash,-1); } }g; int main() { // freopen("1.txt","r",stdin); while(~scanf("%d %d",&N,&K)) { g.clear(); g.Hash[g.Encode(0)] = 0; for(int i=0;i<K;i++) x[0][i] = 0; for(int i=1;i<=N;i++) { scanf("%d",&t); for(int j=0;j<K;j++) { g.arr[i][j] = t%2; t >>= 1; x[i][j] = x[i-1][j] + g.arr[i][j]; g.arr[i][j] = x[i][j] - x[i][0]; } g.insert(i); } printf("%d\n",g.ans); } return 0; }
13. POJ 3349 Snowflake Snow Snowflakes
题意:给定N(N<=10W)片雪花,每片雪花有六个花瓣(我姑且这样叫吧),问能否找出两片完全一样的雪花。
分析:哲理我也是灵光一闪想到了只有六片花瓣,果断sort排序然后字符串Hash搞之,自己瞎搞写了个HashTable解决了下冲突问题,2200ms+A掉了,1A的感觉其实还是相当爽的。
题解:其实我忽略了一个问题,就是如何判断两个雪花是否完全一样,,后来去看别人的题解才知道,要考虑顺时针和你时针的问题,我一个排序恰好没有这个问题了。。。。
后来发现的问题,如果再仔细考虑的话,确实存在这个问题。雪花的结构不能破坏啊。。。
比如像所有数的和,所有数平方的和,这种哈希方法不可行。 从小到大的方法也不可行。因为已经破坏了雪花的结构了 2 1 2 3 4 5 6 3 1 2 4 6 5 照道理说应该输出:No two snowflakes are alike.
主要问题 相同雪花判断,Hash函数设计,冲突问题解决。
POJ某人总结的作法: 1. 直接相加, 把(总和%大质数)为key. 2. 平方和相加, 把(总和%大质数)为key. 3. 从小到大的顺序, 对v[i]<<(i*3)依次异或, 然后模一个大质数作为key.(by hust07p43) 4. 六个数中非零数的积再乘上一个大质数,然后模一个100w上下的数。 自己拿随机数据测下来110w左右的效果最好,随机情况下数据量是10w的时候hash值相同的情况只有6k多个,几乎可以忽略。(by killertom) 5. 依次对每个数异或所得的数作为key. (by archerstarlee) 6. (a[0] + a[2] + a[4])&(a[1] + a[3] + a[5]), 再模一个大质数. 中间的&还可以改成'|' 或者'^'.非常巧妙! 我采用'^'得到最快的719ms. (只对本题适用的hash方法) 其实最关键的就是要开放式寻址解决hash冲突, 不要以为hash就能解决问题了. 最后就是用getchar和gets来进行输入转换更为快速. G++比GCC快一些. 这里的话我强行用了BKDRHash,然后用了排序。 至于getchar读入的话,可以学习学习
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define M 300007 int x[10],n; struct HashTable { int Hash[M],ans[M][6]; bool flag; int BKDRHash() { int seed = 131; int key = 0; for(int i=0;i<6;i++) key = key*seed + x[i]; return (key&0x7FFFFFFF) % M; } void insert() { if(flag) return; int key = BKDRHash(); while(Hash[key] != -1) { int i; for(i=0;i<6;i++) { if(x[i] != ans[key][i]) break; } if(i == 6) { flag = true; return; } key++; } Hash[key] = 1; for(int i=0;i<6;i++) { ans[key][i] = x[i]; } } void init() { flag = false; CLR(Hash,-1); } }g; int main() { while(~scanf("%d",&n)) { g.init(); for(int i=0;i<n;i++) { for(int i=0;i<6;i++) scanf("%d",&x[i]); sort(x,x+6); g.insert(); } if(g.flag) puts("Twin snowflakes found."); else puts("No two snowflakes are alike."); } return 0; }
14. POJ 2503 Babelfish
题意:每一个火星文,对应一个翻译过来的单词,出入一个火星文,翻译为正常单词
分析:在没有学习Hash之前,一看这题想都不用想就会去写trie来A,实际上我也是这么干的
题解:这题用字符串Hash来写,不需要处理冲突,我觉得应该是数据比较弱。这也说明了,Hash并不一定是正确的,但是有时候真的很好用。这道题不解决冲突也A了,但是用起来确实有点虚
总的来说还是字符串Hash + 避免冲突
#include <iostream> #include <cstdio> #include <cstring> using namespace std; #define TABLE_SIZE 200003 struct Node { char key[12], fore[12]; bool flag; Node () { flag = false; } } table[TABLE_SIZE]; unsigned int BKDRHash(char *str) { unsigned int seed = 131; unsigned int hash = 0; while (*str) { hash = hash * seed + (*str++); } return (hash & 0x7FFFFFFF) % TABLE_SIZE; } void insert(char *s1, char *s2) { int pos = BKDRHash(s2), m = 0; while (table[pos].flag) { pos += 2 * (++m) - 1; if (pos >= TABLE_SIZE) pos -= TABLE_SIZE; } strcpy (table[pos].key, s2); strcpy (table[pos].fore, s1); table[pos].flag = true; } int main() { char buff[50], s1[20], s2[20]; while (gets(buff) && buff[0] != '\0') { sscanf (buff, "%s %s", s1, s2); insert (s1, s2); } while (scanf ("%s", s1) != EOF) { int pos = BKDRHash(s1), m = 0; bool flag = false; while (table[pos].flag) { if (strcmp(table[pos].key, s1) == 0) { printf ("%s\n", table[pos].fore); flag = true; break; } else { pos += 2 * (++m) - 1; pos = (pos >= TABLE_SIZE ? pos - TABLE_SIZE : pos); } } if (!flag) printf ("eh\n"); getchar(); } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAXN 100005 #define MOD 200003 typedef struct ENTRY { char e[11]; char f[11]; int next; }Entry; Entry entry[MOD]; int cnt = 1; int HashIndex[MOD]; char str[30]; int ELFHash(char* key) { unsigned long h = 0; while(*key) { h = (h << 4) + (*key++); unsigned long g = h & 0Xf0000000L; if(g) h ^= g >> 24; h &= ~g; } return h % MOD; } void find(char* f) { int Hash = ELFHash(f); for(int k = HashIndex[Hash];k;k=entry[k].next) { if(strcmp(f,entry[k].f) == 0) { printf("%s\n",entry[k].e); return; } } printf("eh\n"); } int main() { while(gets(str)) { if(*str == '\0') break; sscanf(str,"%s %s",entry[cnt].e,entry[cnt].f); int Hash = ELFHash(entry[cnt].f); entry[cnt].next = HashIndex[Hash]; HashIndex[Hash] = cnt++; } while(gets(str)) find(str); return 0; }
#include <cstdio> #include <cstring> #include <algorithm> //#include <malloc.h> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) struct Trie { struct Node { int cnt; Node* next[26]; char dir[12]; }; Node* root; void Init() { root = CreateNode(); } Node* CreateNode() { Node* tmp = (Node*)malloc(sizeof(Node)); for(int i=0;i<26;i++) tmp->next[i] = NULL; tmp->cnt = 0; return tmp; } void Insert(char* str,char* dir) { Node* p = root; while(*str) { int t = *str - 'a'; if(p->next[t] == NULL) p->next[t] = CreateNode(); p = p->next[t]; str++; } p->cnt++; strcpy(p->dir,dir); } void Search(char* str) { Node* p = root; while(*str && p) { p = p->next[*str - 'a']; str++; } if(p && p->cnt) printf("%s\n",p->dir); else printf("eh\n"); } void Delete(Node* rt) { if(rt == NULL) return; for(int i=0;i<26;i++) { if(rt->next[i]) Delete(rt->next[i]); } free(rt); } }Trie; char s1[20],s2[20]; int main() { // freopen("1.txt","r",stdin); Trie.Init(); while(gets(s1) && *s1 != '\0') { int i; for(i=0;s1[i]!=' ';i++); s1[i] = '\0'; strcpy(s2,s1+i+1); Trie.Insert(s2,s1); } while(~scanf("%s",s1)) { Trie.Search(s1); } Trie.Delete(Trie.root); return 0; }
15. POJ 3261 Milk Patterns
题意:实际上就是给定一个长度为N的字符串(这里用的是整型数组存放的)和一个k,在串中找到出现次数≥k的长度最长的子串
分析:多项式hash + 二分答案,处理一下judge,注意二分,然后结束
题解:这道题目正统的作法应该是后缀数组,无奈自己现在还不会,现在学习的是tao哥的多项式hash+二分来做,体会多项式hash的强大以及二分答案的快感。
正统作法 后缀数组, 学习的作法 多项式Hash + 二分答案
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define x 123 #define maxn 20020 int n,k; int s[maxn]; unsigned long long Hash[maxn], h[maxn], xp[maxn]; int rank[maxn]; int cmp(int a,int b) { if(Hash[a] == Hash[b]) return a < b; else return Hash[a] < Hash[b]; } bool judge(int l) { int c = 0; for(int i=0;i+l-1<n;i++) { rank[i] = i; Hash[i] = h[i] - h[i+l] * xp[l]; } sort(rank,rank+n-l+1,cmp); for(int i=0;i+l-1<n;i++) { if(i == 0 || Hash[rank[i]] != Hash[rank[i-1]]) c = 0; if(++c >= k) return true; } return false; } int main() { while(~scanf("%d %d",&n,&k)) { for(int i=0;i<n;i++) scanf("%d",&s[i]),s[i]++; h[n] = 0; for(int i=n-1;i>=0;i--) h[i] = h[i+1] * x + s[i]; xp[0] = 1; for(int i=1;i<=n;i++) xp[i] = xp[i-1] * x; int l = 1, r = n; while(l <= r) { int m = (l + r) >> 1; if(judge(m)) l = m + 1; else r = m - 1; } printf("%d\n",l-1); } return 0; }