【小结】manacher
现在要解决下列问题:
给定字符串 \(s\),求 \(s\) 的最长回文子串的长度。
首先,暴力是 \(\mathcal O(n^3)\) 的。
然后暴力加上哈希是 \(\mathcal O(n^2)\) 的。
然后奇偶性分类并二分哈希是 \(\mathcal O(nlogn)\) 的。
那有没有线性做法呢?
有的有的,就是 manacher。
首先考虑一个更有思维性的 \(\mathcal O(n^2)\) 暴力。
选取中间点,往两边扩展,奇偶分类一下。
首先对于奇偶性分类太麻烦了,有没有小技巧?
有的有的,在每一个字符两边插入 \(#\)(开头结尾也要插入)。
然后以每一个位置为中间位置,往两边找回文串。
然后有一个定义:最长回文半径,表示往左/往右延伸的最大长度。设 \(i\) 作为中间的最大回文半径为 \(p_i\)。那么整个串的长度为 \(2p_i-1\),\(#\) 比有效字符多一个,所以 \(\frac{(2p_i-1-1)}{2}=\frac{2p_i-2}{2}=p_i-1\) 就是对应原串的长度。
现在的目标转化为了求最长回文半径。
设前面的所有 \(i\) 之中,最大的结尾为 \(r\),对应的 \(i\) 为 \(mid\) 。
此时我们用这对 \(mid,r\) 来计算 \(i\) 的答案。
首先 \(i>mid\),因为 \(mid\) 是在 \(i\) 前面枚举到的。
接下来,分两种情况:
-
\(i>r\),直接 \(p_i=1\),从 \(1\) 开始枚举,因为没有任何可以继承的东西了。
-
\(i\leq r\)。此时 \(i\) 关于 \(mid\) 的对称点为 \(2\times mid-i\),如果 \(p_{2\times mid-i}< r-i+1\),那么关于 \(mid\) 对称过来就变成了 \(p_i\)。否则,直接继承到 \(r-i+1\),因为后面的不一定对称。接下来枚举。
其实就是确定 \(p_i\) 的下界,往上枚举。
注意更新 \(mid,r\)。
复杂度分析:
对于 \(i>r\),每一次跳都会使得 \(r\) 增加。
对于 \(i\leq r\),如果 \(p_{2\times mid-i}< r-i+1\),那么下一个直接停止(因为肯定不一样),否则每一次都会拓展。也会让 \(r\) 增加。
总之,就是每一次枚举,都会让 \(r\) 增加。
\(r\) 最多增加 \(n\) 次,所以时间复杂度:\(\mathcal O(n)\)。
P3805 【模板】manacher
模板题,一模一样。
#include<bits/stdc++.h>
using namespace std;
/*
*/
struct FSI{
template<typename T>
FSI& operator >> (T &res){
res=0;T f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
res*=f;
return *this;
}
}scan;
const int N=1.1e7+10,M=2*N;
int n,i,m;
char s[N],t[M];
int p[M];
void manacher()
{
int i,mid=0,r=0,ans=0;
t[++m]='#';
for (i=1;i<=n;i++)
{
t[++m]=s[i];
t[++m]='#';
}
for (i=1;i<=m;i++)
{
if (i<=r) p[i]=min(p[2*mid-i],r-i+1);
else p[i]=1;
while (i-p[i]>=1&&i+p[i]<=m&&t[i-p[i]]==t[i+p[i]]) p[i]++;
ans=max(ans,p[i]-1);
if (i+p[i]-1>r) r=i+p[i]-1,mid=i;
}
printf("%d\n",ans);
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
manacher();
return 0;
}
P11749 「TPOI-1C」Standard Problem.
下文令 \(A\&B\) 表示 \(A\) 与 \(B\) 拼接之后,开头在 \(A\) 结尾在 \(B\) 的方案数。
首先,有递推公式:
\(S^1=1\)
\(S^2=S^1+2+1\&2\)
\(S^3=S^2+3+2\&3+1\&2\&3\)
\(\dots\)
差分,得到:
\(S^2-S^1=2+1\&2\)
\(S^3-S^2=3+2\&3+1\&2\&3\)
\(\dots\)
\(S^k-S^{k-1}=k+(k-1)\&k+\dots+1\&2\&\dots\&k\)
显然,这些东西的和加上 \(S^1\) 就是答案。
首先考虑 \(1,2,3,\dots,k\) 都长一样,所以长度相同的 \(1,2,3,\dots,k\),\(1\&2,2\&3,\dots,(k-1)\&k\) 诸如此类都一样。
再考虑一个惊人的结论:
\(1\&2\&3=1\&2\&3\&4=1\&2\&3\&4\&5=\dots\)
这是为啥呢?因为 \(s\) 和 \(ss\) 和 \(sss\) 回文等价。
所以对于两边的每一对位置,中间都是等价的。
处理出 \(S1,S2,S3\)。
最后答案:
\(S1+(S2-S1)\times(k-1)+\frac{(k-1)\times(k-2)}{2}\times (S3-2\times S2+S1)\)。
最后那个东西咋来的?我们要求 \(1\&2\&3\)。
也就是 \(S^3-S^2-(S^2-S^1)=S^3-2\times S^2+S^1\)。
时间复杂度:\(\mathcal O(n)\)。使用马拉车。
#include<bits/stdc++.h>
using namespace std;
/*
*/
struct FSI{
template<typename T>
FSI& operator >> (T &res){
res=0;T f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
res*=f;
return *this;
}
}scan;
typedef long long ll;
const int N=9e6+10,M=2*N,mod=998244353;
int T,n,i;
string s;
char t[M];
int p[M];
ll S1,S2,S3,ans,k;
ll manacher(string s)
{
int i,n=s.size(),mid=0,r=0,m=0;
ll ans=0;
t[++m]='#';
for (i=0;i<n;i++)
{
t[++m]=s[i];
t[++m]='#';
}
for (i=1;i<=m;i++)
{
if (i<=r) p[i]=min(p[2*mid-i],r-i+1);
else p[i]=1;
while (i-p[i]>=1&&i+p[i]<=m&&t[i-p[i]]==t[i+p[i]]) p[i]++;
ans=(ans+p[i]/2)%mod;
if (i+p[i]-1>r) r=i+p[i]-1,mid=i;
}
return ans;
}
void work()
{
cin>>n>>k>>s;
S1=manacher(s);
S2=manacher(s+s);
S3=manacher(s+s+s);
ans=(S1+(k-1)*((S2-S1+mod)%mod)%mod+(k-1)*(k-2)/2%mod*((S3-2*S2+S1+mod+mod)%mod)%mod)%mod;
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while (T--) work();
return 0;
}

浙公网安备 33010602011771号