传送门
题目大意:
给出一个长度为 n n n 的字符串,问至少在原串的末尾添加多少个字符可以使这个串变为回文串。
题意转换:
在原串的基础之上,求包含末尾字符的最长的回文子串,若该最长回文子串的长度为 l e n len len,则答案 a n s = n − l e n ans = n - len ans=n−len;
解题思路:
算法:字符串哈希(双向哈希)
看一波范围, n ∈ 4 e 5 n∈4e5 n∈4e5。求最长回文子串,普通的做法是两层for循环来求,显然在该数据范围下不能使用该做法。那就换;有一个比较优秀的算法是马拉车算法 ( M a n a c h e r ‘ s A l g o r i t h m Manacher‘s\ Algorithm Manacher‘s Algorithm),可以在 O ( n ) O(n) O(n) 的时间复杂度下求出最长回文子串。确实比较优秀哈,但是,该算法貌似也仅仅能求最长回文子串,相关的扩展问题几乎没有,可以算的上冷门算法,在此也就不对马拉车算法做相关解释了(其实是博主还没有学会,逃。。。)
接下来且看本文核心内容:
关于该题,使用字符串哈希的算法来解,简直太方便了。由于该最长回文子串必须包含最后一个字符,也就是说,它的位置已经是确定的了。这样的话,利用字符串哈希同样可以在 O ( n ) O(n) O(n) 的时间复杂度下求出最长回文子串。
字符串哈希,利用p进制的思想来表示字符串。
此题比较巧妙的是使用了双向哈希。这样可以在 O ( 1 ) O(1) O(1) 的时间复杂度判断某个串是否是回文子串。
我个人感觉不必对子串的长度奇偶性进行考虑,判断左半边和右半边的子串是否相等,这里的一半,我们取 ⌈ n 2 ⌉ \lceil{\frac{n}{2}}\rceil ⌈2n⌉, n n n 是要判断是否是回文串的那个字符串长度。
字符串哈希不会怎么办? 这就去学习
上代码:
#include
using namespace std;
typedef unsigned long long ull;
const int P = 131;
const int N = 400010;
ull hl[N],p[N],hr[N];
ull get(ull h[],int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
char str[N];
int main()
{
int n; scanf("%d",&n);
scanf("%s",str + 1);
p[0] = 1;
for(int i = 1,j = n;i <= n;i++,j--)
{
hl[i] = hl[i - 1] * P + str[i];
hr[i] = hr[i - 1] * P + str[j];
p[i] = p[i - 1] * P;
}
for(int i = 1;i <= n;i++)
{
int len = n - i + 1;
len /= 2;
if(get(hl,i,i + len - 1) == get(hr,1,len))
{
cout << i - 1 << endl;
break;
}
}
return 0;
}
/*
a#b#b#b#b
123456789
*/
另外还有一道题,比这个题稍微复杂一点点。
传送门
解题思路:
算法:字符串哈希 + 二分(双向哈希)
这个题的数据范围是 1 e 6 1e6 1e6, 而且最长回文子串的位置不固定,要找到最长回文子串的话就需要加上二分来判断了,二分最长回文子串的半径,很明显可以知道二分半径是具有单调性的,半径小的不是回文串的话,半径更大的时候必定也是非回文的。
为了便于计算回文串的长度,我们将原字符串做以下处理,在每两个字符之间添加一个 ‘#’,这样的话,处理所有的字符串的时候,我们面临的全部都是长度为奇数的字符串(偶数长度的子串必定不是回文串例如:a#a#或#a#a)。若出现回文子串,只能出现两种形式:
1、#a#…#a# 去 掉 特 殊 字 符 后 , 回 文 串 的 长 度 与 半 径 相 等 去掉特殊字符后,回文串的长度与半径相等 去掉特殊字符后,回文串的长度与半径相等
2、a#…#a 去 掉 特 殊 字 符 后 , 回 文 串 的 长 度 等 于 半 径 加 1 \ \ \ 去掉特殊字符后,回文串的长度等于半径加1 去掉特殊字符后,回文串的长度等于半径加1
对于所有符合回文条件的串的长度取最大值即为答案。
上代码:
#include
#include
#include
#include
using namespace std;
typedef unsigned long long ull;
const int N = 2000010;
char str[N],s[N];
const int P = 131;
ull p[N],hl[N],hr[N];
ull get_hash(ull h[],int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int k = 1;
while(scanf("%s", str + 1), strcmp(str + 1, "END"))
{
int n = strlen(str + 1);
int len = 0;
for(int i = 1;i <= n;i++)
{
s[++len] = str[i];
if(i < n) s[++len] = '#';
else s[len + 1] = '\0';
}
p[0] = 1;
for(int i = 1,j = len;i <= len && j >= 1;i++,j--)
{
p[i] = p[i - 1] * P;
hl[i] = hl[i - 1] * P + s[i];
hr[i] = hr[i - 1] * P + s[j];
}
int ans = 0;
for(int i = 1;i <= len;i++)
{
int l = 0,r = min(len - i,i - 1);
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(get_hash(hl,i - mid,i - 1) != get_hash(hr,len - i - mid + 1,len - i)) r = mid - 1;
else l = mid;
}
if(s[i - l] == '#') ans = max(ans,l);
else ans = max(ans,l + 1);
}
printf("Case %d: %d\n",k++,ans);
}
return 0;
}
/*
#a#b#a#
a#a#a#a
1234567
*/
再送一道比较基础的字符串哈希题
单向哈希即可。
模板题,比较基础,我直接放上代码了。(不懂没关系,会用就很棒)
上代码:
#include
#include
#include
#include
using namespace std;
const int N = 100010;
int P = 131;
typedef unsigned long long ull;
ull p[N],h[N];
char str[N];
ull get_hash(int l,int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int n,m; cin >> n >> m;
cin >> str + 1;
p[0] = 1;
for(int i = 1;i <= n;i++)
{
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
for(int i = 1;i <= n;i++)
{
cout<<get_hash(i,i)<<" ";
}
cout<<endl;
while(m--)
{
int l1,r1,l2,r2;
cin >> l1 >> r1 >> l2 >> r2;
if(get_hash(l1,r1) == get_hash(l2,r2)) puts("Yes");
else puts("No");
}
return 0;
}
特别提醒:危!!!p[0]一定要初始化为1呀。(忘记好几次,得调半天,呜呜呜)