字符串(づ。◕‿‿◕。)づ入门之章

字符串(づ。◕‿‿◕。)づ入门之章

一、KMP算法

久仰大名的算法O(∩_∩)O可以分为两个部分,一部分是创造部分匹配表,一部分是对比,可以理解为先和自己比再和别人比,因为篇幅关系这里就不展开去细讲KMP算法了,已经有很多大佬讲得很棒了。

1、【模板】KMP字符串匹配

题目描述

给出两个字符串 s1 和 s2,若 s1 的区间 [l, r][l,r] 子串与 s2 完全相同,则称 s2 在 s1 中出现了,其出现位置为 l
现在请你求出 s2​ 在 s1​ 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2​,你还需要求出对于其每个前缀 s′ 的最长 border t′ 的长度。

输入格式

第一行为一个字符串,即为 s1。
第二行为一个字符串,即为 s2​。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2 在 s1 中出现的位置。
最后一行输出 |s2​∣ 个整数,第 i 个整数表示 s2​ 的长度为 i 的前缀的最长 border 长度。

输入输出样例
输入 #1
ABABABC
ABA
输出 #1
1
3
0 0 1 
说明/提示
样例 1 解释

img

对于 s2 长度为 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。

数据规模与约定

本题采用多测试点捆绑测试,共有 3 个子任务

  • Subtask 1(30 points):∣s1∣≤15,∣s2∣≤5。
  • Subtask 2(40 points):∣s1∣≤104,∣s2∣≤102。
  • Subtask 3(30 points):无特殊约定。

对于全部的测试点,保证 1≤∣s1∣,∣s2∣≤10^6,s1,s2 中均只含大写英文字母。

完全的模板,就是要输出KMP算法中的一些关键参数。

AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
//KMP
//存储部分匹配表
int kmp[2000000];
string s1,s2;
//创建部分匹配表
void kmp_createNext(string s1, string s2){
    int l2 = s2.length();
    int k = 0;
    for(int i = 1; i < l2; i++){
        while(k && s2[i] != s2[k]){
            k = kmp[k];
        }
        if(s2[i] == s2[k]){
            kmp[i+1] = ++k;
        }
        else{
            kmp[i+1] = 0;
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> s1 >> s2;
    kmp_createNext(s1, s2);
    int k = 0;
    int l1 = s1.length();
    int l2 = s2.length();
    for(int i = 0; i < l1; i++){
        while(k && s1[i] != s2[k]){
            k = kmp[k];
        }
        if(s1[i] == s2[k]){
            k += 1;
        }
        if(k == l2){
            cout<< i - l2 + 2 <<endl;
        }
    }
    for(int i = 1; i <= l2; i++){
        cout<<kmp[i]<<" ";
    }
    return 0;
}
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}

2、[NOI2014] 动物园

题目描述

近日,园长发现动物园中好吃懒做的动物越来越多了。例如企鹅,只会卖萌向游客要吃的。为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习算法。

某天,园长给动物们讲解KMP算法。

园长:“对于一个字符串S,它的长度为L。我们可以在O(L)的时间内,求出一个名为next的数组。有谁预习了next数组的含义吗?”

熊猫:“对于字符串S的前ii个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作next[i]。”

园长:“非常好!那你能举个例子吗?”

熊猫:“例Sabcababc,则next[5]=2。因为S的前5个字符为abcabab既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出next[1] = next[2] = next[3] = 0,next[4] = next[6] = 1,next[7] = 2,next[8] = 3。”

园长表扬了认真预习的熊猫同学。随后,他详细讲解了如何在O(L)的时间内求出next数组。

下课前,园长提出了一个问题:“KMP算法只能求出next数组。我现在希望求出一个更强大num数组一一对于字符串S的前ii个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作num[i]。例如Saaaaa,则num[4]=2。这是因为S的前4个字符为aaaa,其中aaa都满足性质‘既是后缀又是前缀’,同时保证这个后缀与这个前缀不重叠。而aaa虽然满足性质‘既是后缀又是前缀’,但遗憾的是这个后缀与这个前缀重叠了,所以不能计算在内。同理,num[1] = 0,num[2] = num[3] = 1,num[5] = 2”

最后,园长给出了奖励条件,第一个做对的同学奖励巧克力一盒。听了这句话,睡了一节课的企鹅立刻就醒过来了!但企鹅并不会做这道题,于是向参观动物园的你寻求帮助。你能否帮助企鹅写一个程序求出num数组呢?

特别地,为了避免大量的输出,你不需要输出num[i]分别是多少,你只需要输出所有(num[i]+1)的乘积,对1,000,000,007取模的结果即可。

输入格式

第1行仅包含一个正整数n ,表示测试数据的组数。
随后n行,每行描述一组测试数据。每组测试数据仅含有一个字符串SS的定义详见题目描述。数据保证S 中仅含小写字母。输入文件中不会包含多余的空行,行末不会存在多余的空格。

输出格式

包含 n 行,每行描述一组测试数据的答案,答案的顺序应与输入数据的顺序保持一致。对于每组测试数据,仅需要输出一个整数,表示这组测试数据的答案对 1,000,000,007 取模的结果。输出文件中不应包含多余的空行。

输入输出样例
输入 #1
3
aaaaa
ab
abcababc
输出 #1
36
1
32 
说明/提示
测试点编号约定
1N≤5,L≤50
2N≤5,L≤200
3N≤5,L≤200
4N≤5,L≤10,000
5N≤5,L≤10,000
6N≤5,L≤100,000
7N≤5,L≤200,000
8N≤5,L≤500,000
9N≤5,L≤1,000,000
10N≤5,L≤1,000,000

这题是对KMP算法的进一步探索,做出来这道题基本就意味着掌握了基本的KMP算法,要求的数组其实就是长度小于一半的和next要求相同的子串的数量,所以我们可以递推,先不管长度,我们在求next的时候同时存一下子串的数量,因为我们每次都会求出字串的长度,而该长度的子串的符合条件的子串数量已知,所以就可以完成递推

然后我们再考虑长度小于一半的问题,我们需要用KMP算法再搜一遍,再次找到最大长度,然后看看,如果过大的话,就用next数组来缩小,一直到合适的长度,然后利用之前存储好的子串数量来计算即可。

AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
ll mod = 1000000007;
int nex[1000009];
ll num[1000009];
void kmp_init(string s, int l){
    int k = 0;
    for(int i = 1 ; i < l; i ++){
//        cout<<"check1"<<endl;
        while(k && s[i] != s[k]){
            k = nex[k];
        }
        if(s[k] == s[i]) {
            k++;
        }
        nex[i+1] = k;
        num[i+1] = num[k] + 1;
    }
}
char s[1000005];
int main()
{
    ios::sync_with_stdio(false);
    int n = r;
    for(int i = 1; i <= n; i++){
        memset(nex,0,sizeof(nex));
//        string s;
//        cin >> s;
        scanf("%s",s);
        int l = strlen(s);
        num[0] = 0;
        num[1] = 1;
        kmp_init(s,l);
        int k = 0;
        ll ans = 1;
        for(int i = 1; i < l; i++){
//            cout<<"check2"<<endl;
            while(s[k] != s[i] && k){
//                cout<<"check3"<<endl;
                k = nex[k];
            }
            if(s[k] == s[i]){
                k++;
            }
            while((k << 1) > (i + 1)){
//                cout<<"check4:"<<k<<"??"<<i<<endl;
                k = nex[k];
            }
            ans *= (num[k] + 1);
            ans %= mod;
        }
        cout<<ans<<endl;
    }
    return 0;
}
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}

3、[BOI2009]Radio Transmission 无线传输

题目描述

给你一个字符串s1,它是由某个字符串 s2 不断自我连接形成的。但是字符串 s2 是不确定的,现在只想知道它的最短长度是多少。

输入格式

第一行一个整数 L,表示给出字符串的长度。

第二行给出字符串 s1 的一个子串,全由小写字母组成。

输出格式

仅一行,表示 s2 的最短长度。

输入输出样例
输入 #1
8
cabcabca
输出 #1
3
说明/提示
样例输入输出 1 解释

对于样例,我们可以利用 abc 不断自我连接得到 abcabcabc,读入的 cabcabca,是它的子串。

规模与约定

对于全部的测试点,保证 1<L≤10^6。

这道题我们求出来最长前缀,然后因为最长前缀一定是去掉了一个循环节,所以用总长度减去最长前缀就是循环节的长度。

AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
int n;
int kmp[1000006];
char s[1000006];
int main()
{
    ios::sync_with_stdio(false);
    scanf("%d%s",&n,s);
    int k = 0;
    for(int i = 1; i < n; i++){
        while(k && s[i] != s[k]){
            k = kmp[k];
        }
        if(s[i] == s[k]){
            kmp[i+1] = ++k;
        }
        else{
            kmp[i+1] = k;
        }
    }
    cout<<n-kmp[n];
    return 0;
}
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}

4、[USACO15FEB]Censoring S

题目描述

Farmer John has purchased a subscription to Good Hooveskeeping magazine for his cows, so they have plenty of material to read while waiting around in the barn during milking sessions. Unfortunately, the latest issue contains a rather inappropriate article on how to cook the perfect steak, which FJ would rather his cows not see (clearly, the magazine is in need of better editorial oversight).

FJ has taken all of the text from the magazine to create the string S of length at most 10^6 characters. From this, he would like to remove occurrences of a substring T to censor the inappropriate content. To do this, Farmer John finds the first occurrence of T in S and deletes it. He then repeats the process again, deleting the first occurrence of T again, continuing until there are no more occurrences of T in S. Note that the deletion of one occurrence might create a new occurrence of T that didn’t exist before.

Please help FJ determine the final contents of S after censoring is complete.

输入格式

The first line will contain S. The second line will contain T. The length of T will be at most that of S, and all characters of S and T will be lower-case alphabet characters (in the range a…z).

输出格式

The string S after all deletions are complete. It is guaranteed that S will not become empty during the deletion process.

题意翻译

Farmer John为他的奶牛们订阅了Good Hooveskeeping杂志,因此他们在谷仓等待挤奶期间,可以有足够的文章可供阅读。不幸的是,最新一期的文章包含一篇关于如何烹制完美牛排的不恰当的文章,FJ不愿让他的奶牛们看到这些内容。

FJ已经根据杂志的所有文字,创建了一个字符串 S ( S 的长度保证不超过 10^6 ),他想删除其中的子串 T ,他将删去 S 中第一次出现的子串 T ,然后不断重复这一过程,直到 S 中不存在子串 T

注意:每次删除一个子串后,可能会出现一个新的子串 T(说白了就是删除之后,两端的字符串有可能会拼接出来一个新的子串 T )。

输入格式:第一行是字符串 S ,第二行输入字符串 T ,保证 S 的长度大于等于 T 的长度, ST 都只由小写字母组成。

输出格式:输出经过处理后的字符串,保证处理后的字符串不会为空串。

Translated by @StudyingFather

输入输出样例
输入 #1
whatthemomooofun
moo
输出 #1
whatthefun

KMP,找前缀,找到就删,然后回溯继续找,所以我们想到了栈这个数据模型,把字符压入栈中,一旦符合要求就出栈,然后继续压栈,继续判断,这道题是对KMP的进一步解析,需要我们进一步了解KMP

AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
string s1;
string s2;
int kmp[1000006];
int st[1000006];
int f[1000006];
int top;
int k;
int main()
{
    ios::sync_with_stdio(false);
    cin >> s1;
    cin >> s2;
    int l1 = s1.length();
    int l2 = s2.length();
    for(int i = 1; i < l2; i++){
        while(k && s2[i] != s2[k]){
            k = kmp[k];
        }
        if(s2[i] == s2[k]){
            k++;
        }
        kmp[i+1] = k;
    }
    k = 0;
    for(int i = 0; i < l1; i++){
        while(k && s1[i] != s2[k]){
            k = kmp[k];
        }
        if(s1[i] == s2[k]){
            k++;
        }
        f[i] = k;
        st[++top] = i;
        if(k == l2){
            top -= l2;
            k = f[st[top]];
        }
    }
    for(int i = 1; i <= top; i++){
        cout<<s1[st[i]];
    }
    return 0;
}
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}

二、manacher算法

线性查找最长回文串,这个算法和KMP算法一样给我感觉很有灵性,也许比赛或者考试之类的不会怎么考这个,但我感觉他的思维很酷,能给我很多启发

1、【模板】manacher算法

题目描述

给出一个只由小写英文字符 a,b,c,…y,z 组成的字符串 S ,求 S 中最长回文串的长度 。

字符串长度为 n

输入格式

一行小写英文字符 a,b,c,…y,z 组成的字符串 S

输出格式

一个整数表示答案。

输入输出样例
输入 #1
aaa
输出 #1
3
说明/提示

1≤n≤1.1×10^7。

这里只提供一个板子,因为确实没把握把这个算法很精简的把要点讲出来

AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
//#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
char s[42000002];
int p[42000002];
int l;
inline void init(){
      char c=getchar();
      s[0]='~',s[l=1]='|';
      while(c<'a'||c>'z') c=getchar();
      while(c>='a'&&c<='z') s[++l]=c,s[++l]='|',c=getchar();
}
int main()
{
    ios::sync_with_stdio(false);
    //记录延伸最远的字符串
    int mx = 0;
    //记录对称中心
    int id = 0;
    //答案
    int ans = 0;
    //初始化分隔符
    init();
    //manacher算法
    for(int i = 1 ; i < l; i++){
        if(i < mx){
            p[i] = min(p[id*2-i],mx - i);
        }
        else{
            p[i] = 1;
        }
        while(s[i + p[i]] == s[i - p[i]]){
            p[i]++;
        }
        if(p[i] + i > mx){
            mx = p[i] + i;
            id = i;
        }
        ans = max(ans,p[i]-1);
    }
    cout<<ans<<endl;
    return 0;
}
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}

三、字符哈希

感觉这个方向的内容还是很宏大的,这里只是提及一下这方面的概念,有机会再展开来个哈希表专题

1、【模板】字符串哈希

题目描述

如题,给定 N 个字符串(第 ii 个字符串长度为 Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N 个字符串中共有多少个不同的字符串。

#友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)

输入格式

第一行包含一个整数 N,为字符串的个数。

接下来 N 行每行包含一个字符串,为所提供的字符串。

输出格式

输出包含一行,包含一个整数,为不同的字符串个数。

输入输出样例
输入 #1
5
abc
aaaa
abc
abcc
12345
输出 #1
4
说明/提示

对于 30% 的数据:N≤10,Mi≈6,Mmax≤15。

对于 70% 的数据:N≤1000,Mi≈100,Mmax≤150。

对于 100% 的数据:N≤10000,Mi≈1000,Mmax≤1500。

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。

就是将字符串的比较压缩成数字之间的比较,这里利用asc码来合成数字,并利用了自然溢出

AC code
#include <bits/stdc++.h>
#define max 50005
//#pragma GCC optimize(3,"Ofast","inline")
//#define re register
//#define ll long long
#define ull unsigned long long
//#define r read()
using namespace std;
速读
//inline int read();
并查集的查找
//int found(int k);
辗转相除法------返回最大公因数
//int gcd(int p,int q);
阶乘
//int fac(int k);
st表
//int st[10000][30];
//int lg[10000];
初始化
//void initST(int n);
查找
//int seekST(int le, int ri);
线性基
//ll p[101];
添加
//void add_key(ll x);
//快速幂
//ll ksm(ll a, ll b){
//    ll c = 1;
//    while(b){
//        if(b % 2 == 1){
//            c *= a;
//        }
//        a *= a;
//        b >>= 1;
//    }
//    return c;
//}
//线性筛
//void xxs(){
//    bool nums[n];
//    for(int i = 2; i <= n; i++){
//        if(!nums[i]){
//            for(int j = i +i; j <= n; j+=i){
//                nums[j] = 1;
//            }
//        }
//    }
//}
ull base = 131;
int ans = 1;
int n;
ull a[10005];
ull hashs(string s){
    int l = s.length();
    ull ans = 0;
    for(int i = 0; i < l; i++){
        ans = ans * base + (ull)s[i];
    }
    return ans % 0x7fffffff;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 0; i < n; i++){
        string s;
        cin >> s;
        a[i] =  hashs(s);
    }
    sort(a,a+n);
    for(int i = 1; i < n; i++){
        if(a[i] != a[i-1]){
            ans++;
        }
    }
    cout<<ans<<endl;
    return 0;
}
速读
//inline int read()
//{
//	int x=0,f=1;char ch=getchar();
//	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
//	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
//	return x*f;
//}
并查集
//int f[1];
//int found(int k){
//    if(f[k] == k){
//        return k;
//    }
//    return f[k] = found(f[k]);
//}
辗转相除法
//int gcd(int p,int q){
//  int t = p % q;
//  return t==0?q:gcd(q,t);
//}
阶乘
//int fac(int k){
//    int ans = 1;
//    for(int i = 1; i<= k; i++){
//        ans *= i;
//    }
//    return ans;
//}
初始化st表
//void initST(int n){
//    for(int i = 1; i <= n; i++){
//        int temp = r;
//        st[i + n][0] = st[i + n + n][0]= st[i][0] = temp;
//    }
//    for(int i = 2; i <= n * 3; i++){
//        lg[i] = lg[i >> 1] + 1;
//    }
//    int ln = lg[n + n + n];
//    for(int i = 1; i <= ln; i++){
//        for(int j = 1; j + (1 << (i - 1)) - 1<= n * 3; j++){
//            st[j][i] = max(st[j][i-1],st[j+(1 << (i - 1))][i-1]);
//        }
//    }
//}
查找st表
//int seekST(int le, int ri){
//    int len = ri - le + 1;
//    int q = lg[len];
//    return max(st[le][q],st[ri - (1 << q) + 1][q]);
//}
添加到线性基
//void add_key(ll x){
//    for(int i = 62; i >= 0; i--)
//	{
//		if(!(x >> (ll)i))
//			continue;
//		if(!p[i])
//		{
//			p[i] = x;
//			break;
//		}
//		x ^= p[i];
//	}
//}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值