字符串学习笔记

哈希

  • 哈希理解起来就相当于用一种映射方式,以实现快速对比字符串或者其他东西(比如图)的功能,有时候利用技巧还能实现字典序大小的比较或者反映射操作。
  • 其思想是非常重要而且容易理解的,关键在于hash方法以及编程上的技巧。
  • 取一固定值\(P\),把字符串看作\(P\)的进制数,代表每种字符。再取一固定值\(M\),求出\(P\)进制位对\(M\)的余数,作为该字串的哈希值。
  • 我们取\(P=233\)\(P=13331\),此时\(Hash\)值产生冲突的概率很低,只要\(Hash\)值相等,我们就可以认为原字符串是相等的。
  • 我们通常取\(M=2^64\),即直接使用unsigned long long类型存储这个\(Hash值\),这种类型自动对\(2^64\)取模,避免低效的mod运算。

实现方法

  • 前缀和是hash的常见用法

方法一

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+100;
const int base=233;
char s[maxn];
int poww[maxn],hash[maxn];
int main(){
    while(cin>>s+1){
        int len=strlen(s+1);
        poww[1]=233;
        for(int i=2;i<=len;i++)
            poww[i]=poww[i-1]*base;
        hash[1]=s[1];
        for(int i=2;i<=len;i++)
            hash[i]=hash[i-1]*base+s[i];
    }
}

方法二

  • 把字符串变成数字
#define ull unsigned long long
ull gethash(string s){
    ull tmp=0;
    for(int i=0;i<s.size();i++)
        tmp=tmp*233+(s[i]-'a');
    return tmp;
}

一道例题

题目

已知字符串A与B,有n组询问,每次询问给l,r,s,t,要求O(1)判断A[l…r]与B[s…t]是否相同。

题解

  • 既然已经求得两个字符串的hash数组,就直接取A[l..r]的哈希值与B[s…t]的哈希值进行比较即可。
  • 那么如何更加方便的比较呢?
  • 我们使用 \(hash[i]=hash[i-1]*10+s[i]\) 的方法进行哈希得哈希数组\(hasha,hashb\)
  • 然后我们发现一个问题:
    • 举一例子:

    \(s1: 1 2 3 4 5 1\)
    \(s2: 2 3 4 1 1 2\)

    • \(s1[2…4]\)\(s2[1…3]\) 是相同的,但我们发现,\(hasha[4]-hasha[1]\)\(hashb[3]-hashb[0]\)并不相同,为什么呢?
    • 回忆我们的哈希过程,发现我们要乘上一个 \(poww[i]\),这样我们求出来的才是真正的哈希,即\(hash[r]-hash[l-1]*pow[i]\)

code

while(n--){
    cin>>l>>r>>s>>t;
    len=r-l+1;
    if(hasha[r]-hasha[l-1]*poww[len] == hashb[s]-hahsb[t-1]*poww[len])
        puts("YES");
    else puts("NO");
}

再来几道例题

manachar

普及知识

  • 回文串:abcba(√),abba(√),abda(×)(就是正着看和倒着看相同的串)
  • 子串≠子序列(子串是连续的,子序列可不连续)

一道例题

求一个字符串中最长回文子串长度。
例:字符串:abbababa   最长回文子串:ababa

题解

  • manacher是一种暴力的优化,可以用O(n)的时间解决上述问题,但是我真的没怎么见过这玩意派上用场
  • 可以学一下回文自动机,那个的作用要比这个大得多...
  • 因为偶回文串比较难处理(不是以某个字符为中心),所以首先我们要通过在每个相邻字符中间插入一个相同的字符(比如’|’,一定要是没有出现过的),这样预处理操作一发后我们会发现所有的回文串都是奇回文串,并且它也不会影响答案。
  • 把预处理后的字符串叫做S
  • 最后的出来的答案就是 S的最长回文子串长+1
  • 复杂度O(n)也是容易理解的,因为R只有一次从1到n,以及i只有一次从1到n,而且二者是并列关系。
  • 此种算法心中有印象即可,十分不常考。

code

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e8+100;
char a[maxn<<1];
int f[maxn<<1];
int main(){
    int cnt=0;a[0]='~';a[++cnt]='|'; char ch; int ans=0;
    while((ch=getchar())>='a' && ch<='z') a[++cnt]=ch,a[++cnt]='|';
    for(int i=1,r=0,mid=0;i<=cnt;i++){ //mid中心,i现在的点,f[]半径 
        if(i <= r) f[i]=min(f[mid*2-i],r-i+1);
        while(a[i-f[i]]==a[i+f[i]]) f[i]++;
        if(f[i]+i>r) r=f[i]+i-1,mid=i;
        if(f[i]>ans) ans=f[i];
    }
    printf("%d\n",ans-1);
    return 0;
}

推荐例题

有兴趣可以做一做洛谷板子题

KMP

讲解

code

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+100;
char a[maxn],b[maxn];
int fail[maxn];
int main(){
	cin>>a+1>>b+1;
	int lena=strlen(a+1),lenb=strlen(b+1);
	for(int i=2,j=0;i<=lenb;i++){
		while(j && b[i] != b[j+1]) j=fail[j];
		if(b[i] == b[j+1]) j++;
		fail[i]=j;
	}	
	for(int i=1,j=0;i<=lena;i++){
		while(j && a[i] != b[j+1]) j=fail[j];
		if(a[i] == b[j+1]) j++;
		if(j==lenb){
			cout<<i-lenb+1<<endl;
			j=fail[j];
		}
	}
	for(int i=1;i<=lenb;i++)
		cout<<fail[i]<<" ";
	
}

推荐题目:落谷板子题

posted @ 2020-07-20 21:43  hyskr  阅读(171)  评论(2编辑  收藏  举报