RK哈希(Rabin_Karp 哈希)
Rabin_Karp 哈希
通过比较hash值是否相等来比较每个字符串是否相等
有概率出错(很小)
字符串x1,x2,x3……xk
基底e;模数mo;
hash=(xk*e^0+xk-1*e^1+......+x1*e^k-1)mod mo
注意:
①字符映射到数字不要映射到0
②基底e>字符种类数
③据说mo数为大素数出错概率小
mo=1000000007(存int64)
mo=666623333
递推建hash:
初始条件:hash[1]=x1;
递推式:hash[i]=(hash[i-1]*e+xi)mod mo;
区间hash:
对于任意关于x的子串xi-xj
hash[i,j]=(xj*e^0+xj+1*e^1+......+xi*e^j-i)mod mo;
O(n)的算法
hash[i,j]=(hash[j]-hash[i-1]*e^(j-i+1) mod mo+mo)mod mo
O(1)的算法
1.子串排序 sort.exe
【题目描述】给出一个长为n的字符串S,和一个数字m,记ssi表示从S的第i位开始、长度为m的子串。如,当S="abcab",m=2时,ssi的值分别为:
ss1="ab"
ss2="bc"
ss3="ca"
ss4="ab"
ss5="b"
你需要把ss1~ssn这n个字符串排序成字典序不降的形式。一开始,ss1~ssn这n个字符串排成一行;你每次只可以交换相邻两个字符串的位置。如,把上述ss1~ss5排好序的一种方案是:
⑴原序列:"ab", "bc", "ca", "ab", "b"
⑵交换第3和第4个串:"ab", "bc", "ab", ca", "b"
⑶交换第2和第3个串:"ab", "ab", "bc", ca", "b"
⑷交换第4和第5个串:"ab", "ab", "bc", b", "ca"
⑸交换第3和第4个串:"ab", "ab", "b", bc", "ca"
并且,上述方案是交换次数最少的方案。
你的任务就是编程求出最少的把所有子串排成字典序不降的形式的交换次数。
NOIP怎么可能会考后缀数组╮(╯_╰)╭
【输入格式】第一行包含两个整数n和m;
第二行包含一个长为n的字符串S。S只由小写字母组成。
【输出格式】一行,包含一个整数,表示最小的交换次数。
【样例输入】
5 2
abcab
【样例输出】4
【样例解释】样例数据就是题目描述中的例子。
【数据范围】
对于20%的数据,有n<=10;
对于40%的数据,有n<=100;
对于60%的数据,有n<=5000;
另有10%的数据,有m<=5;
另有10%的数据,字符串S是完全随机生成的;
对于100%的数据,有1<=m<=n<=50000
【解】注意直接存字符串+逆序对70分爆空间
●如果不是排字符串而是排数字?
●给出长为n的数字序列,只能交换相邻的元素,问最少交换几次变成不降?
●经典问题–求逆序对数
●证明:
交换相邻两个元素(左大右小)正好减少一对逆序对;
没必要交换左小右大的元素;
排好序的序列逆序对数为0。
●程序实现:归并排序
◎归并排序时比较两个数字的大小 à 归并排序时比较两个字符串的字典序大小
◎决定两个字符串字典序大小的是从左到右第一位不相同的字符
◎二分+哈希找到第一位不相同的位
◎数字O(1)比较大小 à 字符串O(logn)比较大小
◎归并排序O(nlogn),总复杂度O(nlog2n)
const mo=1000000007; maxn=50005; var f,s,tmp:array[0..maxn]of longint; n,m,i,ans,e:longint; hash,pow:array[0..maxn]of int64; t:array[1..1000000]of longint; ch:char; function pd(i,j:longint):boolean; var l,r,mid:longint; hsi,hsj:int64; begin if i=j then exit(true); l:=0; r:=m+1; if n-j+2<r then r:=n-j+2; if n-i+2<r then r:=n-i+2; while r-l>1 do begin mid:=(l+r) div 2; hsi:=hash[i+mid-1]-hash[i-1]*pow[mid]mod mo; if hsi<0 then hsi:=hsi+mo; hsj:=hash[j+mid-1]-hash[j-1]*pow[mid]mod mo; if hsj<0 then hsj:=hsj+mo; if hsi=hsj then l:=mid else r:=mid; end; if l=m then exit(true); exit(s[i+l]<s[j+l]); end; procedure msort(l,r:longint); var m,i,j,k:longint; begin if l=r then exit; m:=(l+r)>>1; msort(l,m); msort(m+1,r); i:=l;j:=m+1;k:=l; while (i<=m) and (j<=r) do begin if pd(f[i],f[j]) then begin t[k]:=f[i];inc(i);inc(k);end else begin t[k]:=f[j];inc(j);inc(k); ans:=ans+m-i+1;//求逆序对的个数 end; end; while i<=m do begin t[k]:=f[i];inc(i);inc(k);end; while j<=r do begin t[k]:=f[j];inc(j);inc(k);end; for i:=l to r do f[i]:=t[i]; end; begin assign(input,'sort.in');reset(input); assign(output,'sort.out');rewrite(output); readln(n,m); e:=27; pow[0]:=1; hash[0]:=0; for i:=1 to n do pow[i]:=pow[i-1]*e mod mo; for i:=1 to n do begin read(ch); s[i]:=ord(ch)-96; hash[i]:=(hash[i-1]*e+s[i])mod mo; f[i]:=i; end; s[n+1]:=0; msort(1,n); writeln(ans); close(input);close(output); end.
2.找循环节 find.exe
如果一个字符串是以一个或者一个以上的长度为k的重复字符串所连接成的,那么这个字符串就被称为周期为k的字符串。例如,字符串”abcabcabcabc”周期为3,因为它是由4个循环”abc”组成的。它同样是以6为周期(两个重复的”abcabc”)和以12为周期(一个循环”abcabcabcabc”)。
写一个程序,读入一个字符串,并测定它的最小周期。
【输入格式】一个最长为 100 50000 的没有空格的字符串。(加点小小的难度,用上面知识求解)
【输出格式】一个整数表示输入的字符串的最小周期。
输入输出样例:
PERIODIC.IN HoHoHo |
PERIODIC.OUT 2 |
【题解】
这道题求的是字符串算法给定一个字符串A,求最短的字符串B,使得A是若干个B连接而成的字符串的前缀,样例:若A=abcabcab则B=abc.(输出循环节长度3)
算法1:暴力n^2(能过len=100的点)
算法2:kmp(好像难了一点)不会qwq! O(n)
算法3:RK哈希(Rabin-Karp哈希):简单一点。。。O(n log n)
方法:
- 枚举循环节长度L
- 用哈希判断A[1…L],A[L+1…2L],A[2L+1…3L]……是否相等
- 最后一个循环节长度可能不足L,特殊判断
lalalalala~~