Manacher模板

字符串哈希

兔子与兔子

题目描述

很久很久以前,森林里住着一群兔子。有一天,兔子们想要研究自己的 DNA 序列。我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 26 26 个小写英文字母),然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。

输入描述:

第一行一个 DNA 字符串 S S S
接下来一个数字 m m m,表示 m m m 次询问。
接下来 m m m 行,每行四个数字 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从 1 1 1 开始编号。
其中 1 ≤ l e n g t h ( S ) , m ≤ 1000000 1 \leq length(S),m \leq 1000000 1length(S)m1000000

输出描述:

对于每次询问,输出一行表示结果。如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)

示例1

输入

aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2

输出

Yes
No
Yes

思路

把字符串看作是一个 131 131 131 进制的正整数(取质数),将字符串中的每个前缀映射为一个 u n s i g n e d   l o n g   l o n g unsigned ~long ~long unsigned long long 类型的数字。那么任意一段字符,都可以通过前缀的哈希值之间转换, O ( 1 ) O(1) O(1) 计算出来。若两个字符串的映射值相同,则视为字符串相同。

最常见的哈希是 10 10 10 进制下,取模一个质数。字符串哈希利用 u l l ull ull 上溢的性质,避免了取模运算。但是 u l l ull ull 类型的最大值不是质数,所以,要把字符串作为质数进制的数。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,P=26;
char s[N]; int m,l1,r1,l2,r2,len;
unsigned long long h[N],p[N],a,b;

signed main(){
	ios::sync_with_stdio(false);
	cin>>(s+1)>>m;
	len=strlen(s+1);
	p[0]=1;
	for(int i=1;i<=len;i++){
		h[i]=h[i-1]*P+s[i]-'a'+1;
		p[i]=p[i-1]*P;
	}
	while(m--){
		cin>>l1>>r1>>l2>>r2;
		if(h[r1]-h[l1-1]*p[r1-l1+1]==h[r2]-h[l2-1]*p[r2-l2+1])
			cout<<"Yes\n";
		else cout<<"No\n";
	}
}

字符串哈希求最长回文

Palindrome

题目描述

Andy the smart computer science student was attending an algorithms class when the professor asked the students a simple question, “Can you propose an efficient algorithm to find the length of the largest palindrome in a string?”
A string is said to be a palindrome if it reads the same both forwards and backwards, for example “madam” is a palindrome while “acm” is not.
The students recognized that this is a classical problem but couldn’t come up with a solution better than iterating over all substrings and checking whether they are palindrome or not, obviously this algorithm is not efficient at all, after a while Andy raised his hand and said “Okay, I’ve a better algorithm” and before he starts to explain his idea he stopped for a moment and then said “Well, I’ve an even better algorithm!”.
If you think you know Andy’s final solution then prove it! Given a string of at most 1000000 1000000 1000000 characters find and print the length of the largest palindrome inside this string.

输入描述:

Your program will be tested on at most 30 30 30 test cases, each test case is given as a string of at most 1000000 1000000 1000000 lowercase characters on a line by itself. The input is terminated by a line that starts with the string “END” (quotes for clarity).

输出描述:

For each test case in the input print the test case number and the length of the largest palindrome.

示例1

输入

abcbabcbabcba
abacacbaaaab
END

输出

Case 1: 13
Case 2: 6

思路

正序用字符串哈希预处理一遍,再逆序预处理一遍。枚举对称中心(可能为字母,也可能为间隙),二分对称半径,用字符串哈希判断正序和逆序是否相同。

时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,P=131;
char s[N]; int len,ca;
unsigned long long pre[N],post[N],p[N];

unsigned long long calc(int l,int r){
	if(l<r) return pre[r]-pre[l-1]*p[r-l+1];
	else{ swap(l,r); return post[l]-post[r+1]*p[r-l+1]; }
}

int main(){
	ios::sync_with_stdio(false);
	while(cin>>(s+1)){
		int mx=0;
		if(s[1]=='E'&&s[2]=='N'&&s[3]=='D') break;
		len=strlen(s+1); p[0]=1;
		for(int i=1;i<=len;i++){
			p[i]=p[i-1]*P;
			pre[i]=pre[i-1]*P+s[i]-'a'+1;
			post[len-i+1]=post[len-i+2]*P+s[len-i+1]-'a'+1;
		}
		for(int i=1;i<=len;i++){
			int l=1,r=min(i,len-i+1);
			while(l<r){
				int mid=(l+r+1)/2;
				if(calc(i,i+mid-1)==calc(i,i-mid+1)) l=mid;
				else r=mid-1;
			}
			mx=max(mx,l*2-1);
			l=0,r=min(i,len-i);
			while(l<r){
				int mid=(l+r+1)/2;
				if(calc(i+1,i+mid)==calc(i,i-mid+1)) l=mid;
				else r=mid-1;
			}
			mx=max(mx,l<<1);
		}
		cout<<"Case "<<++ca<<": "<<mx<<"\n";
	}
}

Manacher

题目描述

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

字符串长度为 n n n

输入格式

一行小写英文字符 a , b , c , ⋯   , y , z \texttt a,\texttt b,\texttt c,\cdots,\texttt y,\texttt z a,b,c,,y,z 组成的字符串 S S S

输出格式

一个整数表示答案。

输入输出样例

输入

aaa

输出

3

说明/提示
1 ≤ n ≤ 1.1 × 1 0 7 1\le n\le 1.1\times 10^7 1n1.1×107.

思路

首先是插入字符 ‘#’,把字符串统一转换为奇数长度。再在首尾插入’^’、’$’(两个字符不能相同,也不能为’#’),目的是防止越界,处理到首尾会发现和每个字母都不相同,退出while(s[i+p[i]]==s[i-p[i]]) p[i]++;这个循环。由于字符串末尾本身就自带一个’\0’字符,所以也可以省略尾部插入字符 ‘$’。

算法的主要思想为,记录右边界最靠右的一个回文范围 [ l , r ] [l,r] [l,r]。当已知 [ 1 , i − 1 ] [1,i-1] [1,i1] 的回文半径,要求出第 i i i 位的半径时,有两种情况:

  1. i < = r i<=r i<=r,这代表着,可以找到 i i i 关于 [ l , r ] [l,r] [l,r] 中心对称的一个点 j j j i i i j j j 在一定半径之内全等。那么j的回文半径p[i]至少和p[j],若i的回文半径超出了[l,r]的范围,还可能会更大。所以初始化 p [ i ] p[i] p[i] p [ j ] p[j] p[j],再暴力求一下它会不会更大。
  2. i > r i>r i>r,那么 i i i 的半径完全是未知,只能暴力求解。

不断更新 [ l , r ] [l,r] [l,r] 区间。

在这里插入图片描述

例如这个字符串,已知前三位的回文半径, [ l , r ] [l,r] [l,r] [ 0 , 4 ] [0,4] [0,4]。现在求第 i i i 位的回文半径 p [ i ] p[i] p[i],那么它至少为 p [ j ] = 2 p[j]=2 p[j]=2,之后再从半径为 2 2 2 开始暴力扩展,最终求得它的回文半径为 4 4 4,更新 [ l , r ] [l,r] [l,r].

在这里插入图片描述

为什么这个充满暴力的算法,复杂度是 O ( n ) O(n) O(n) 呢。可以发现一个规律,若是暴力拓展 p [ i ] p[i] p[i] 成功,必然会更新 [ l , r ] [l,r] [l,r] 的范围;不更新 [ l , r ] [l,r] [l,r] 的范围的时候,肯定是第一次拓展就失败了。 r r r 不断向右移动, i i i 也向右移动,复杂度 O ( 2 n ) O(2n) O(2n)

#include<bits/stdc++.h>
using namespace std;
const int N=1.2e7;
char s[N<<1]; int cnt,p[N<<1],mx;

void read(){
	s[0]='^',s[cnt=1]='#'; char ch=getchar();
	while(ch>'z'||ch<'a') ch=getchar();
	while(ch>='a'&&ch<='z') s[++cnt]=ch,s[++cnt]='#',ch=getchar();
	s[++cnt]='$';
}

int main(){
	read();
	for(int i=1,mid=0,r=0;i<=cnt-1;i++){
		if(i<=r) p[i]=min(p[(mid<<1)-i],r-i+1);
		while(s[i+p[i]]==s[i-p[i]]) p[i]++;
		if(p[i]+i-1>r) r=i+p[i]-1,mid=i;
		mx=max(mx,p[i]);
	}
	cout<<(mx-1)<<"\n";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_51864047

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值