P4555 [国家集训队] 最长双回文串 踢姐
P4555 [国家集训队] 最长双回文串 踢姐
简要题意:
给定一个字符串 \(S\) ,我们定义字符串 \(T\) 的双回文子串为:存在两个字符串 \(X\) 与 \(Y\) 是 \(T\) 的非空子串,满足 \(X\) 与 \(Y\) 无重叠部分并且两个字符串均为回文串,求给定字符串中出现的最长双回文子串长度。
思路分析:
我们发现很难直接确立最终分割点,我们观察可得对于字符串 \(S\) 中的一个分割点可以确立唯一最大长度,故我们尝试对每一个分割点求出答案,即找到分割点 \(i\) 左右两侧的回文串里,左右边界恰好抵达点 \(i\) 的长度尽可能大的回文串的长度加和,接下来便可以使用Manacher直接求出每一个回文中心可以拓展出的最大回文串长度 \(p_i\) ,记录对于回文中心 \(i\) 拓展出的最大回文串的左右边界到了哪里,并记录对于以 \(i\) 为回文中心扩展得到的最长回文串左右边界到达的分割点,我们开两个数组 \(lc_i\) 与 \(rc_i\) 分别记录左/右边界到达分割点 \(i\) 的回文串的回文中心编号,我们自然希望对于某个分割点,其左/右侧拼接的回文串尽可能长,那么对于两个回文串左边界都到达了 \(i\) 时,我们贪心的选取编号更大的,因为回文中心距离更远,故这个回文串更长,反之同理。
此时我们会发现一些问题,对于如下样例:
baacaabbacabb
我们会发现我们匹配得到的结果是 baacaab + bb ,很玄幻,我们期望是匹配到 baacaab + bacab 或者 aacaa + bbacabb ,但是很显然我们没有匹配到,我们的贪心思路并没有问题,此时我们发现对于该字符串的回文子串 baacaab ,按照我们的思路会拓展 \(lc_1\) 与 \(rc_{15}\) ,bbacabb 会拓展 \(lc_{13}\) 与 \(rc_{27}\) ,而 aacaa 理应拓展 \(lc_3\) 与 \(rc_{13}\) ,但我们输出 \(lc_3\) 发现如下结果:
4
说明 \(lc_3\) 在程序里只能由 a 得到,我们发现,按照我们的思路,我们会拓展对于回文中心 \(i\) 构成的最长回文串的左右边界,但并没有拓展以 \(i\) 作回文中心可以构成的其他回文串,依据我们在 P1659 [国家集训队] 拉拉队排练 踢姐 中得到的结论,即每一个以 \(i\) 为回文中心的最长回文串一定包含 \(\lceil \frac{p_i}{2} \rceil\) 个回文子串,由回文的性质可得,回文子串的左右边界一定是 \([x+c:y-c]\) ,长度一定是 \(p_i-2c\) ,且一定没有除了以 \(i\) 为回文中心的最长回文串包含的回文串以外更多的回文串,故我们也需要更新以 \(i\) 为回文中心的最长回文串包含的所有回文子串的左右边界到达的分割点数组,不难发现这个时间接近 \(\text O(n^2)\) ,我们考虑优化。
我们考虑在记录编号时,对于 \(lc\) 是越大越好,\(rc\) 是越小越好,并且对于每一次更新,我们只更新 \(lc\) 的 \([i-p_i+1,i]\) ,\(rc\) 的 \([i,i+p_i-1]\) ,并且只用取最大最小值,我们直接使用线段树维护(其实完全有前缀和思想的线性解法,不过我没想出来导致复杂度得带一个 \(\text{log}\) ,这里且算是提供一个次优解)即可。
代码实现:
Manacher得到 \(p_i\) 后,我们写两个线段树分别维护区间最大值与区间最小值,支持区间修改与单点查询,每次修改我们需要的区间,并在枚举分割点的时候单点查询出枚举到的分割点的值,最后求出答案即可。
🐎:
struct Seg{
//线段树实现,这里略
#define ls (u<<1)
#define rs (u<<1|1)
int tr[__],tag[__]; // t=0 max | t=1 min
inline void rebuild(int n,int t){...}
inline void pushup(int u,int t){...}
void build(int u,int l,int r,int t){...}
inline void pushdown(int u,int t){...}
void update(int u,int l,int r,int lt,int rt,int x,int t){...}
int query(int u,int l,int r,int id,int t){...}
}lc,rc;
signed main(){
#ifdef Zyhx
freopen("hack.in","r",stdin);
#endif
ios::sync_with_stdio(0),cin.tie(0);
int i,j,k,l,r,x,y,z; n=5; x=1;
cin>>g;
l=strlen(g); s[0]='#',s[n=1]='|';
for(i=0;i<l;++i) s[++n]=g[i],s[++n]='|'; s[++n]='$';
lc.rebuild(n,0),rc.rebuild(n,1);
// 这里是初始化tag
lc.build(1,1,n,0),rc.build(1,1,n,1);
for(i=2;i<n;++i){
if(i<=rt) p[i]=min(p[(md<<1)-i],rt-i+1); else p[i]=1;
for(;i+p[i]<=n&&i+p[i]>=0&&s[i+p[i]]==s[i-p[i]];++p[i]);
if(i+p[i]>rt) rt=i+p[i]-1,md=i;
lc.update(1,1,n,i-p[i]+1,i,i,0);
rc.update(1,1,n,i,i+p[i]-1,i,1);
}
for(i=3;i<n-1;i+=2){
x=lc.query(1,1,n,i,0),y=rc.query(1,1,n,i,1);
if(x!=0&&y!=inf&&x-i+i-y>ans) ans=x-i+i-y;
}
cout<<ans<<endl;
return 0;
}
浙公网安备 33010602011771号