题目大意
给定一个首尾相接的项链,共有 n 位,每一位都是一个小写字母。我们要剪掉连续的一段(注意首尾相接,首尾也是连续的),使得剩余部分拼接起来能够对称(注意首尾相接,重要的事情说三遍)。最大化剩余串长度。
题目分析
简化模型
这题看起来和GDKOI2015Day1T1很像,都是对称。对称即是双回文串(想想为什么)。上一次我们是使用
Manacher
算法解决的,这次我们做同样的考虑。
显然我们先要将其倍长,然后将其变成
Manacher
串(相邻两位和首尾加入#字符)。直接找双回文串就行了?显然不对,长度有时会大于
n
。那长度大于
那么题目模型现在就变成找出该串里面长度小于等于
我们将这个约束用数学符号表示。对于回文中心
i
和
所以现在问题变成求满足
j−i≤n
且
i+pi≤j−pj
的数对
(i,j)
中最大的
j−i
。
这个可以使用各种数据结构如线段树、树状数组等解决,但是在这里我要说一种更机智的解法。
扫描线&并查集
扫描线算法的基本思想是扫描处理询问,同时修改询问位置影响到的值。
我们考虑从左到右枚举位置
x
,如果存在
这个怎么实现呢?我们开一个黑白点数组,初始时所有位置都为白点。我们处理询问
i
,就是查询从位置
怎么办?我们改变枚举顺序,从右到左枚举,我们就可以将删除改为添加,使用并查集维护。因为反着枚举的话我添加的点都是非法的,然后合法解显然为一连串非法解的左边第一个(特判自己本身合法的情况)。
于是时间复杂度就变成
O(nα(n))
了。
具体细节见代码实现。
代码实现
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=400500;
struct list
{
int last[N],next[N],adj[N];
int tot;
void insert(int x,int y)
{
adj[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
}q,c;
int p[N],fa[N];
char s[N],S[N];
bool color[N];
int n,ans,l;
int getfather(int son)
{
return fa[son]==son?son:fa[son]=getfather(fa[son]);
}
void manacher()
{
int pos=0,lim=1;
p[0]=1;
for (int i=1;i<l;i++)
{
if (i<lim)
p[i]=min(p[(pos<<1)-i],lim-i);
while (i-p[i]>=0&&i+p[i]<l&&S[i-p[i]]==S[i+p[i]])
p[i]++;
if (i+p[i]>lim)
pos=i,lim=i+p[i];
}
for (int i=0;i<l;i++)
p[i]--;
}
int get(int x)
{
if (!color[x])
return x;
x=getfather(x);
return x-1;
}
void merge(int x,int y)
{
int fx=getfather(x),fy=getfather(y);
fa[fy]=fx;
}
int main()
{
freopen("necklace2.in","r",stdin);
freopen("necklace2.out","w",stdout);
scanf("%s",s);
n=strlen(s);
for (int i=n;i<n<<1;i++)
s[i]=s[i-n];
s[n<<1]='\0';
n<<=1;
l=n<<1|1;
for (int i=0;i<n;i++)
S[i<<1]='#',S[i<<1|1]=s[i];
S[l-1]='#';
manacher();
for (int i=0;i<l;i++)
fa[i]=i;
for (int i=0;i<l;i++)
{
c.insert(i-p[i],i);
q.insert(i+p[i],i);
}
for (int i=l-1;i>=0;i--)
{
int j=q.last[i],x;
while (j)
{
x=q.adj[j];
int y=get(min(l-1,x+n/2));
ans=max(ans,y-x);
j=q.next[j];
}
j=c.last[i];
while (j)
{
x=c.adj[j];
color[x]=1;
if (x-1>=0&&color[x-1])
merge(x-1,x);
if (x+1<l&&color[x+1])
merge(x,x+1);
j=c.next[j];
}
}
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}