字符串Hash
Emmmmm
1.关于字符串Hash值过大,可以再hash一下变小,采用双hash处理即可
经典题型,数串a在串b中出现的次数
题目链接 https://vjudge.net/problem/POJ-3461
思路1 计算b的字符串hash前缀和,如果sum[r]-sum[l]*b^l==a的hash值,ans++
核心代码
scanf("%s%s",ar+1,br+1);
ull same=0,l1=strlen(ar+1),l2=strlen(br+1),l=1;
for(ull i=1;i<=l1;i++) same=same*b+ar[i],l*=b;
for(ull i=1;i<=l2;i++) sum[i]=sum[i-1]*b+br[i];
ull ans=0;
for(ull i=l1;i<=l2;i++) if(sum[i]-sum[i-l1]*l==same) ans++;
cout<<ans<<endl;
思路2 套用KMP,定义串s为a+’#’+b,’#'为分隔符,只要为a,b中都不会出现的字符即可,这样前缀函数最大也就为a的长度,把前缀函数中值为la的个数数出来即可。
核心代码
ll kmp(string s,ll lb) {
ll ans=0;
ll l = s.length();
memset(pi,0,sizeof(pi));
for (ll i = 1; i < l; i++) {
ll j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
if(j==lb) ans++;
}
return ans;
}
int main(){
std::ios::sync_with_stdio(false);
//std::cin.tie(nullptr);
ll t;
cin>>t;
while(t--){
string sra,srb;
cin>>sra>>srb;
srb=sra+"#"+srb;//加个分界符
kmp(srb,sra.length());
}
return 0;
}
总结 两种方法时间复杂度差不多,kmp并没有比第一种方法好多少,所以如果hash函数好的话,直接用第一种就够了,如果被卡hash了,再用kmp,kmp是肯定能解,不会出现意外的。
经典题型,求长度为n的不同子串个数
题目链接 https://vjudge.net/problem/POJ-1200
思路
拿到这道题可能会想要统计每种子串hash值的出现次数,那么就需要把字符串的hash值再hash到一个小范围去,但其实不需要,因为我们不需要统计每种子串hash值的出现次数,只需要将这些子串放到一个数组中,再去除掉重复就行了。
计算b的字符串hash前缀和,用sum[r]-sum[l]*b^l求出每个长度为l的子串的hash值,并存到数组中,因为是要统计不同子串数量,就需要去除重复,再给数组sort下,再unique下就可以了。
核心代码
cin>>n;//子串长度为n
scanf("%s",br+1);//注意这里是br+1
ull l2=strlen(br+1),l=1;
for(ll i=1;i<=n;i++) l*=b;
for(ull i=1;i<=l2;i++) sum[i]=sum[i-1]*b+br[i];
for(ull i=n;i<=l2;i++) ans[i-n+1]=sum[i]-sum[i-n]*l;
sort(ans+1,ans+l2-n+2);
ll length=unique(ans+1,ans+l2-n+2)-(ans+1);
cout<<length<<endl;
求至少出现m次的子串的最大长度
题目链接 https://vjudge.net/problem/HDU-4080
题意 给你一个长度<4*10^4的字符串,求至少出现m次的子串的最大长度,以及其在最右边出现的下标。
思路 * 要找最长长度l,就可以考虑二分枚举长度l
对于每次l的枚举,记录至少出现m次的最后下标即可
对于字符串Hash值过大,可以再hash一次来存储,即双hash
坑点 只开了10的5次方大小的数组来存hash值,想都不用想肯定会冲突啊!!!
当时自己没想到,就疯狂套各种base,以为是base的问题
也不静下新来想一想,要冷静啊,大兄弟!!!
A~H8个字母按字典序排序与排序序号的双射
题意 令A对应1,B对应2,,,,,,H对应8,AA对应9,AB对应10,,,,将其字典序排序与其序号相互映射。
思路 字典序排序映射到序号很简单,就是字符串Hash就行了,但是将序号映射到字符串就复杂一些。因为A~H有八个字母,其排序相当于是个8进制,但是发现数字8(10)=10(8),10是 s两个字母,而H对应8是一个字母,这难在模8时有0这个数字。那怎么处理呢?你想,对于8,模8时这一位为0,那我如果让这一位变成8,不就相当于向高位借了1个1嘛,那让高位减个1就可以了。
代码
while(ans){
ll a=ans%8;
if(a==0) a=8,ans-=8;//向高位借了一位,所以要减8
ar[step++]=a;
ans/=8;
}
for(ll i=step-1;i>=0;i--) printf("%c",ar[i]+'A'-1);
字典树
优点:插入和查询效率很快
适用于串很多,但串不长的情况
缺点:空间消耗较大
当hash函数很好时,查找效率低于hash
字符串长不能太长,否则效率低,时间复杂度过。
初始化速度优化
常规初始化:
inline void init(){
memset(nex,0,sizeof(nex));
tmp=1;
}
优化的初始化:
inline void init(){
nex[0][0]=nex[0][1]=0;
tmp=1;
}
inline void insert(ll n) { // 插入整数n
ll p = 0;
for (ll i = 31; i >= 0; i--) {
ll c = (n>>i)&1;
if (!nex[p][c]){
memset(nex[tmp],0,sizeof(nex[tmp]));//初始化速度优化
nex[p][c] = tmp++;
}
p = nex[p][c];
}
}
经典题型,字符串前缀
题目链接 https://vjudge.net/problem/POJ-2001
题意 找出每个字符串的最短前缀
思路 对于每个字符串,前缀的意思就是,对于这个字符串,以每个字符作为结尾都是存在的。所以对于唯一最小前缀,就是以这个前缀为结尾的数量为1个,或者是它本身。
核心代码
inline void insert(string s) { // 插入字符串s,无返回值
ll p = 0,l=s.length();
for (ll i = 0; i < l; i++) {
ll c = s[i] - 'a';//如果不全是小写字母,要改!!!
if (!nex[p][c]){
cnt[tmp]=0;//初始化
exist[tmp]=false;//初始化
memset(nex[tmp],0,sizeof(nex[tmp]));//初始化
nex[p][c] = tmp++; // 如果没有,就添加结点
}
p = nex[p][c];
cnt[p]++;
}
exist[p] = true;
}
inline bool found(string s) { // 查找字符串s,找到返回true,否则false
ll p = 0,l=s.length();
for (ll i = 0; i < l; i++) {
ll c = s[i] - 'a';
if (!nex[p][c]) return false;
p = nex[p][c];
}
return exist[p];
}
inline void init(){//初始化
nex[0][0]=nex[0][1]=0;
tmp=1;
}
经典题型,异或
题目链接 https://vjudge.net/problem/HDU-4825
题意
对于一个整数集合S,有m次询问,每次询问一个数字k,求集合S中与k异或为最大值的那个数
特点 一个数与一个集合的数去异或的最大值
思路 异或,可以看成二进制串,因为是求最大值,所以我们得从最高位开始异或,尽量保证每次高位异或值为1,这样子求得的就是最大值。
核心代码
inline void insert(ll n) { // 插入32位!!!整数n,无返回值
ll p = 0;
for (ll i = 31; i >= 0; i--) {//
ll c = (n>>i)&1;
if (!nex[p][c]){
cnt[tmp]=0;//初始化
exist[tmp]=false;//初始化
memset(nex[tmp],0,sizeof(nex[tmp]));//初始化
nex[p][c] = tmp++;
}
p = nex[p][c];
cnt[p]++;
}
exist[p]=true;
}
inline void init(){
nex[0][0]=nex[0][1]=0;
tmp=1;
}
inline ll solve2(ll n) { // 返回字典树中与32位整数n异或为最大值的那个数
ll p = 0,ans=0;
for (ll i = 31; i >= 0; i--) {
ll c = !((n>>i)&1);
if (!nex[p][c]){
p=nex[p][!c];
ans=ans*2+!c;
}
else{
p = nex[p][c];
ans=ans*2+c;
}
}
return ans;
}
带删除操作的01字典树
题目链接 https://vjudge.net/problem/HDU-5536
题意 从n个数中,选择3个不同的数a,b,c,求(a+b)XORc的最大值
思路 在01字典树的基础上增加了一个删除a,b的操作
不需要真的删除a,b结点,当cnt[p]==0时,也可以视作删除了
核心代码
inline void insert(ll n) { // 插入32位!!!整数n,无返回值
ll p = 0;
for (ll i = 31; i >= 0; i--) {//
ll c = (n>>i)&1;
if (!nex[p][c]){
cnt[tmp]=0;//初始化
exist[tmp]=false;//初始化
memset(nex[tmp],0,sizeof(nex[tmp]));//初始化
nex[p][c] = tmp++;
}
p = nex[p][c];
cnt[p]++;
}
exist[p]=true;
}
inline void del(ll n){//从字典树中删除32位整数n
//不是完全删除,用cnt[p]==0时来标记其被删除!!!
ll p = 0;
for (ll i = 31; i >= 0; i--) {
ll c = (n>>i)&1;
p = nex[p][c];
cnt[p]--;
}
}
inline bool found(ll n) { // 查找整数n,找到返回true,否则false
ll p = 0;
for (ll i = 31; i >= 0; i--) {
ll c = (n>>i)&1;
if (!nex[p][c]) return false;
p = nex[p][c];
}
return exist[p];
}
inline void init(){
nex[0][0]=nex[0][1]=0;
tmp=1;
}
本文详细介绍了字符串Hash的应用技巧及注意事项,包括解决经典问题如字符串匹配与计数等。同时深入探讨了字典树的实现方法及其在不同场景下的高效应用。
8255

被折叠的 条评论
为什么被折叠?



