题解:AT_nikkei2019_2_final_h 逆にする関数
更好的阅读体验
这题,真厉害。这题可能可以帮助你发现,你并没有学懂 manacher 算法。
首先,我们回顾一下 manacher 算法的流程(仅考虑求奇回文串):
- 记
d_i 为以i 为中心的最大回文半径。 - 从左往右枚举回文中心
i ,同时维护区间[l, r] 表示已经找到的回文串中,右端点最靠右的一个。 - 若当前
i > r ,则让d_i 从1 开始暴力扩展。d_i 每增加1 都会使r 增加1 ,因此这个操作是均摊O(n) 的。 - 若当前
i \le r ,假设[l, r] 中心为x ,则i 处的d 值可以由i 关于x 的对称点,也就是l + r - i 的d 值继承得到。- 若
d_{l+r-i} \le r-i+1 ,则由对称性,继承完这个值后i 处的最长回文半径不可能增加;若增加则说明d_{l+r-i} 不是l+r-i 处的最长回文半径。 - 否则,我们只能确定
r-i+1 一定是i 处一个合法的回文半径,令其为d_i 初值。在此基础上我们再暴力扩展d_i 。易发现每扩展1 都会引起r 加1 ,因此这个操作也是均摊O(n) 的。
- 若
事实上,维护回文信息只是 manacher 应用场景的一个特殊情况。观察以上算法的流程,我们发现:
- 要求可以暴力扩展,也就是说,若
[l, r] 合法,则[l-1, r+1] 合法。 - 要求可以把一段区间的信息关于一个点作对称, 进行继承。也就是说,如果
[L, R], [l, r] 均合法([l, r] \subseteq [L, R] ),则[l, r] 关于[L, R] 中点的对称区间[l', r'] 也合法。
我们来看这个题。容易发现本题中的信息满足以上两个条件。首先考虑找出所有的合法区间,则只需要解决“判断一个区间
则考虑对于一个有解的区间
我们回顾普通的 manacher 算法在继承一步时怎么做的:让
接下来说明这个算法仍然是线性时间。
原命题相当于证明,整个算法过程中,
\max \{0, d_{l + r - i} - (r - i + 1)\} 之和为O(n) 。假设
[l, r] 的回文中心为x ,则l = 2x - r, l + r - i = 2x - i 。则由于
[l, r] 是目前最右的回文串,因此必然有d_{2x - i} + 2x-i< d_x + x 。记
k = i - x 。则2x - i = x - k 。代入上式有d_{x - k} + x - k < d_x+ x, d_{x - k} < d_x + k 。接下来考虑需要进行回缩操作的条件:
i + d_{2x - i} > x + d_x 。可以得到
d_{x - k} > d_x + x - i = d_x - k 。因此-k < d_{x - k} - d_x < k 。再考虑我们回缩的量,也就是
i + d_{2x - i} - (x + d_x) 的值。该式可以变形得到k + d_{x - k} - d_x < 2k 。而由于回缩后已经满足d_i = r-i+1 ,因此此时i + d_i - 1 已经成为了右端点最靠右的回文串之一(这个“之一”意味着,我们在回缩操作后,无论d_i 是否再增加,都要把i + d_i - 1 设置为新的r ,否则复杂度不正确)。因此我们使用了至多2k 次移动的代价,使r 增加了至少k 。因此算法的复杂度仍为
O(n) 。
问题在线性时间内得到解决。
#include<bits/stdc++.h>
#define endl '\n'
#define N 300006
#define MOD 998244353
using namespace std;
inline void add(int &x,int y) {x+=y,x-=x>=MOD?MOD:0;};
inline void dec(int &x,int y) {x+=MOD-y,x-=x>=MOD?MOD:0;}
int n,m,pw[N],a[N],buc[N],pre[N],nxt[N];
int d1[N],d2[N],f1[N],f2[N],g1[N],g2[N];
int refl(int l,int r,int x) {return r-(x-l+1)+1;}
int check(int l,int r)
{
if(l>r)return 1;
int ok=1;
ok&=(pre[r+1]<l-1||a[refl(l-1,r+1,pre[r+1])]==a[l-1]);
ok&=(nxt[l-1]>r+1||a[refl(l-1,r+1,nxt[l-1])]==a[r+1]);
return ok;
}
void upd(int &fv,int &gv,int l,int r)
{
if(nxt[l]>r-1)gv++; if(l!=r&&pre[r]<l)gv++;
add(fv,pw[m-gv]);
}
void del(int &fv,int &gv,int l,int r)
{
dec(fv,pw[m-gv]);
if(nxt[l]>r)gv--; if(l!=r&&pre[r]<l+1)gv--;
}
void manacher()
{
for(int i=1;i<=m;i++)buc[i]=0;
for(int i=1;i<=n;i++)pre[i]=buc[a[i]],buc[a[i]]=i;
for(int i=1;i<=m;i++)buc[i]=n+1;
for(int i=n;i;i--)nxt[i]=buc[a[i]],buc[a[i]]=i;
for(int i=1,l=0,r=-1;i<=n;i++)
{
int k;
if(i>r)k=1,upd(f1[i],g1[i],i,i);
else {
k=d1[l+r-i],f1[i]=f1[l+r-i],g1[i]=g1[l+r-i];
int re=l+r-i;
while(k>r-i+1)del(f1[i],g1[i],re-k+1,re+k-1),k--;
}
while(1<=i-k&&i+k<=n&&check(i-k+1,i+k-1))k++,upd(f1[i],g1[i],i-k+1,i+k-1);
d1[i]=k;
if(i+k>=r)l=i-k+1,r=i+k-1;
}
for(int i=1,l=0,r=-1;i<=n;i++)
{
int k;
if(i>r)k=0;
else {
k=d2[l+r-i+1],f2[i]=f2[l+r-i+1],g2[i]=g2[l+r-i+1];
int re=l+r-i+1;
while(k>r-i+1)del(f2[i],g2[i],re-k,re+k-1),k--;
}
while(1<=i-k-1&&i+k<=n&&check(i-k,i+k-1))k++,upd(f2[i],g2[i],i-k,i+k-1);
d2[i]=k;
if(i+k>=r)l=i-k,r=i+k-1;
}
}
main()
{
scanf("%d%d",&n,&m),pw[0]=1;
for(int i=1;i<=m;i++)pw[i]=1ll*m*pw[i-1]%MOD;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
manacher(); int ans=0;
for(int i=1;i<=n;i++)add(ans,f1[i]);
for(int i=1;i<=n;i++)add(ans,f2[i]);
printf("%d\n",ans);
return 0;
}