看到回文串,于是就想到了马拉车。
马拉车可以帮我们求出每个 \(i\) 的最大扩展距离,容易得出,双回文串就是两个回文串拼一起。当然,两个回文串必须要相交,不然形不成一个字符串。
有的小可爱就会想直接找最大的两个扩展距离然后比一比,但是这是不行的。
因为两个回文串的相交部分必须砍成两个刚好相交的字符串,两个回文串也会因此而缩小。例如如下例子:
aaabaaabaaa
看起来两个回文串扩展距离都有 3 ,但中间是重叠的。最终分出的字符串只能是 aabaaaba
,其中 aabaa
和 aba
是两个回文串。
如果我们按照上面的规则不难发现:两个中心点中间的一段就是实际上回文串的扩展距离,所以只要把中间距离 \(*2\) 就行。
这也告诉我们一件事情,就是在所有满足条件的中心点里面,离当前中心点最远的中心点最优。
不妨将每个点i只向后比较,那么我们要的就是在 log 的时间内快速找到最长回文串左端点在 i-hw[i] 左边(hw[i]为以i为中心的最大扩展距离,就是马拉车求的那个东西)的最大点。
一开始的想法是在平衡树上二分,但是带了两个log。后来想到可以用权值线段树解决。下标为每个点为中心点的最长回文串的左端点,权值为中心点。从最后的字符开始枚举,每次在1~hw[i]中找最大值。之后将 i 的左端点加入线段树。
由于添加了分隔符,所以最终答案要除2。发现前面有个乘2,干脆消掉好了。
但是这还不够。思考一件事情,如果新添加的左端点比原先的任意一个左端点还要大,那么肯定永远不会选到。如果我们把这些点删去且保持下标不变,就会得到一个权值单调上升的序列。每一个点的最优点为序列中下标小于该点右端点的最大值。
这样子可以得到一个常数很小的二分。但是还不够。
我们可以先把最终得到的单调上升的序列跑出来,用类似于并查集的手法处理一下,然后正着跑。这时序列中的点会不断减少(因为我们是倒着统计的),每减少一个点,就参照并查集的写法将两个区间合并。尽管这种做法对应的并查集只能进行路径压缩,但由于不能卡,并且开始可以用线性时间处理完第一波路径压缩,所以时间复杂度接近 \(O(nα(n))\) 按道理来说会比上面两种做法跑的都快,而且常数还很小。
好家伙,别人还在写带字符集大小复杂度的回文自动机呢,你的时间复杂度已经出现了一个逼格满满的α,这不显得很高级?为想出了一种并没有什么用的做法找借口
但我们有线性做法,我们处理出每一个字符串最长回文串的左端点和右端点,之后再跑一遍马拉车,但是这一次我们倒着跑。根据我们在二分做法中得出的结论,我们可以在每一个点进行一个一个更新时寻找那些没被标记过的右端点,如果左端点小于这些右端点则标记。如果有值呢?有值我们不需要管。因为这意味着一个距离更远的点能满足条件。时间复杂度和马拉车复杂度相同,为线性。
对于整个串为回文串的情况要单独处理,因为必须要能分成两个回文串。
注意一件事情,string在本地ubuntu跑的时候啥问题没有,但是在洛谷上对其中单个字符直接赋值就会变成精致小垃圾,私吞你的分数,还留下一两个AC让你不明所以。所以直接赋值最好用char。
这里只有权值线段树的做法
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = 1e5 + 5;
int hw[maxn << 1], n, b[maxn << 1];
char s[maxn << 2];
void change(string S)
{
s[0] = s[1] = '$';
for(int i = 0;i < n;i ++)
{
s[i * 2 + 2] = S[i];
s[i * 2 + 3] = '$';
}
n = n * 2 + 2;
s[n] = 0;
return ;
}
void maracher()
{
int maxr = 0,mid = 114514;
for(int i = 1;i < n;i++)
{
if(i < maxr) hw[i] = min(hw[(mid << 1) - i],hw[mid] + mid - i);
else hw[i] = 1;
for(;s[i + hw[i]] == s[i - hw[i]];++ hw[i]) ;
if(hw[i] + i > maxr)
{
maxr = hw[i] + i;
mid = i;
}
}
return ;
}
bool ton[maxn << 1];
struct linetree
{
int tr[maxn << 3];
#define ls(u) u<<1
#define rs(u) (u<<1)+1
void push_up(int u){ tr[u] = max(tr[ls(u)], tr[rs(u)]); }
void change(int p, int l, int r, int fl, int k)
{
if(l > fl || r < fl) return ;
if(l == r)
{
tr[p] = k;
return ;
}
int mid = l + r >> 1;
change(ls(p), l, mid, fl, k);
change(rs(p), mid + 1, r, fl, k);
push_up(p);
return ;
}
int query(int p, int l, int r, int fl, int fr)
{
if(l >= fl && r <= fr)
{
return tr[p];
}
if(l > fr || r < fl) return 0;
int mid = l + r >> 1, ans = 0;
ans = max(ans, query(ls(p), l, mid, fl, fr));
ans = max(ans, query(rs(p), mid + 1, r, fl, fr));
return ans;
}
}T;
char S[maxn];
bool hui()
{
int n = strlen(S);
for(int i = 0;i < n - i - 1;i ++) if(S[i] != S[n - i - 1]) return 0;
return 1;
}
signed main()
{
scanf("%s", S);
n = strlen(S);
int p = n;
change(S);
maracher();
int ans = 0;
for(int i = n - 1;i > 0;i --)
{
hw[i] --;
int j = T.query(1, 1, n, 1, i + hw[i]);
if(j != 0) ans = max(ans, (j - i));
if(ton[i - hw[i]] == 0)
{
ton[i - hw[i]] = 1;
T.change(1, 1, n, i - hw[i], i);
}
}
if(ans == p && hui()) cout << ans - 1 << endl;
else cout << ans << endl;
return 0;
}
标签:int,P4555,hw,解题,端点,ans,fl,回文
From: https://www.cnblogs.com/closureshop/p/16871070.html