什么是哈希啊?html
哈希是一种用来统计复杂数据的不完美算法,或者说思想,构造一个哈希函数将原始数据映射成便于统计的信息上,在映射过程当中会损失部分信息。相似于离散化,仅保留大小关系。ios
举个栗子:git
维护一个数据结构,支持插入一个数,查询一个的数在这个数据结构中的个数,数的大小 \(\le 2^{63} - 1\)算法
把插入的一个数对一个不是很大的数取模,令新数代替原数。
若是两个数的余数相等,则认为它们两个相等
开一个 \(cnt\) 数组统计个数数组
同时对多个模数取模,判重时判取模后的全部数是否所有相等,
实现时能够定义一个结构体,用 \(set/map\) 维护,或写哈希表。数据结构
正确性大幅增长,但仍不是彻底正确。
通常写双哈希就够了,卡不掉。函数
考虑单哈希。
不把余数相等的一些数直接看作相等,开个链表把它们链起来。
判重时找到查询的数的余数对应的链表,遍历全部元素判重。
能够用邻接表或 vector 实现。优化
随机数据下链表最大长度(每次判重的复杂度)指望 O(\frac{n}{mod})。
牺牲了时间,保证了正确性ui
能够用多哈希使数的分布更加均匀。
通常作法是对哈希获得的多个余数再进行哈希。spa
对应的哈希函数相等是两元素相等的必要条件
能够随便构造
构造哈希函数的方式多种多样,模一个数,乘一个数,加一个数,甚至更复杂的关系
只要正确性高就行,或者函数符合您的品味
用于判重字符串
将一串字符映射成一个整数再进行判断
因为字符串是具备先后关系的,通常按下述方法构造:
选取两个合适的互质常数 \(b\) 和 \(h (b < h)\), 假设有一个字符串 \(C = c_1c_2···c_m\),那么咱们定义哈希函数:
考虑递推实现,设 \(H(C, k)\) 为前 \(k\) 个字符构成的字符串的哈希值,则:
一般,题目要求的是判断主串的一段字符与另外一个匹配串是否匹配,即判断字符串 \(C = c_1c_2···c_m\) 从位置 \(k + 1\) 开始的长度为 \(n\) 的子串 \(C^{'} = c_{k + 1}c_{k + 2}···c_{k + n}\) 的哈希值与另外一匹配串 \(S = s_1s_2···s_n\) 的哈希值是否相等,则:
只要预求得 \(b^{n}\) ,就能 \(O(1)\) 判断了
总时间复杂度 \(O(n + m)\)
板子题
/* Work by: Suzt_ilymics Knowledge: ?? Time: O(??) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl #define int long long using namespace std; const int MAXN = 1e6+10; const int INF = 1; const int mod = 1e9+9; const int b = 1e9+7; char A[MAXN], B[MAXN]; int H[MAXN], h[MAXN], pow, cnt = 0, sum = 0; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } int quickpow(int x, int p, int mod){ int res = 1; while(p){ if(p & 1) res = res * x % mod; x = x * x % mod; p >>= 1; } return res; } #undef int int main() { #define int long long // init(); cin>>A + 1; cin>>B + 1; int lenA = strlen(A + 1), lenB = strlen(B + 1); pow = quickpow(b, lenB, mod); for(int i = 1; i <= lenA; ++i){ H[i] = (H[i - 1] * b % mod + A[i]) % mod; } for(int i = 1; i <= lenB; ++i){ sum = (sum * b % mod + B[i]) % mod; } cnt = 0; for(int i = 0; i + lenB <= lenA; ++i){ // cout<<(H[i + lenB] - H[i] * pow % mod + mod) % mod<<endl; if((H[i + lenB] - H[i] * pow % mod + mod) % mod == sum){ // orz; cnt++; } } printf("%d", cnt); return 0; }
发现图书只有加入没有拿出,用字符串哈希转换后和上面的例题相似
用 bool
数组来表示其是否加入,\(O(1)\) 查询
考虑用双哈希优化,会使重复的可能性降到很低
我这里只用了单哈希,开到一亿多才卡过去
成为全部提交记录中用时最长空间最长的一份代码
/* Work by: Suzt_ilymics Knowledge: ?? Time: O(??) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 3e4+4; const int INF = 1; const int mod = 101451419; const int b = 1e9 + 7; LL n, stc[MAXN], sc = 0; bool vis[101452419]; char nam[210], opt[10]; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } int main() { n = read(); for(int i = 1, len; i <= n; ++i){ LL sum = 0; cin>>opt; gets(nam + 1); len = strlen(nam + 1); for(int j = 1; j <= len; ++j){ sum = (sum * b % mod + nam[j]) % mod; } if(opt[0] == 'a') vis[sum] = 1; if(opt[0] == 'f') if(vis[sum]) printf("yes\n"); else printf("no\n"); } return 0; }
比较好出思路,枚举重复的字符串的长度,由于长度必须是总长度的因子,因此 \(O(len)\) 枚举便可,而后扫一遍看看是否符合条件,从小到大最早遇到的必定是答案
/* Work by: Suzt_ilymics Knowledge: ?? Time: O(??) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 1e6+6; const int INF = 1; const int mod = 1e9+9; const int b = 1e7+7; char s[MAXN]; LL pow[MAXN], H[MAXN], sum; int len; int read(){ int s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar(); return f ? -s : s; } void init(){ pow[0] = 1; for(int i = 1; i <= 1e6; ++i){ pow[i] = pow[i - 1] * b % mod; } } bool check(int mid){ sum = H[mid]; for(int i = 0; i <= len - 1; i += mid){ if((H[i + mid] - H[i] * pow[mid] % mod + mod) % mod != sum) return 0; } return 1; } int main() { init(); while((cin>>(s + 1)) && s[1] != '.'){ len = strlen(s + 1); for(int i = 1; i <= len; ++i){ H[i] = (H[i - 1] * b % mod + s[i]) % mod; } for(int i = 1; i <= len; ++i){ if(len % i) continue; if(check(i)) { printf("%d\n", len / i); break; } } } return 0; }
相关 \(Solution\) 请跳转个人这篇题解
来自两篇题解的思路,能够结合着理解一下,另外loj上这题卡我模数和进制数
一、循环节必定是长度的约数 二、若是n是一个循环节,那么k*n也一定是一个循环节(关键所在) 三、n是[l,r]这一段的循环节 的充要条件是 [l,r-n]和[l+n,r]相同(利用这个性质咱们在判断是否为循环节是能够作到O(1)) 因此咱们能够在求出这个区间的长度以后,判断它的每一个约数是不是循环节(应用性质3),而且由于性质1,它的约数是循环节,原串必定也是。
循环节的长度的循环次数都必定是总长的约数 个人作法是把总长除掉循环次数 先把len分解质因数 (线性筛质数,并记录下每一个数的最小质因子加速分解,这已是常规操做了) 由于最小循环节的倍数也是循环节 因此从len开始试除每一个质因子并判断(你能够理解为len的因子分为循环节的因子和循环次数的因子,要把循环次数的因子除掉)
/* Work by: Suzt_ilymics Knowledge: ?? Time: O(??) */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define LLL unsigned long long #define orz cout<<"lkp AK IOI!"<<endl using namespace std; const int MAXN = 5e5+10; const int INF = 1; const int mod = 1e9+7; const int b = 7; LL n, m; char s[MAXN]; LLL Pow[MAXN], H[MAXN], sum; LL prim[MAXN], sc = 0; bool vis[MAXN], flag; LL read(){ LL s = 0, f = 0; char ch = getchar(); while(!isdigit(ch)) f |= (ch == '-'), ch = getchar(); while(isdigit(ch)) s = (s << 3) + (s << 1) + ch - '0', ch = getchar(); return f ? -s : s; } void init(){ for(LL i = 2; i <= n; ++i){ if(vis[i]) continue; for(LL j = 1; i * j <= n; ++j){ LL t = i * j; if(vis[t]) continue; vis[t] = true; prim[t] = i; } } } int main() { n = read(); init(); cin >> (s + 1); Pow[0] = 1; for(LL i = 1; i <= n; ++i){ Pow[i] = Pow[i - 1] * b, H[i] = H[i - 1] * b + s[i]; } m = read(); for(LL i = 1, l, r, len, ans; i <= m; ++i){ l = read(), r = read(); ans = len = r - l + 1; while(len > 1){ LL k = ans / prim[len]; len /= prim[len]; if(H[r - k] - H[l - 1] * Pow[r - k - l + 1] == H[r] - H[l - 1 + k] * Pow[r - k - l + 1]){ ans = k; } } printf("%d\n", ans); } return 0; }