【做题】BZOJ2342 双倍回文——马拉车&并查集

题意:有一个长度为(n)的字符串,求它最长的子串(s)满足(s)是长度为4的倍数的回文串,且它的前半部分和后半部分都是回文串。

(n leq 5 imes 10^5)

首先,显然要用manacher求出以每一位为中心的最长回文串。考虑枚举(s)最中间的一个位置(i),且以其为中心的最长回文串长度为(2 r_i),那么,前半部分的中心(j)就必须在([i - lfloor frac {r_i} {2} floor,i))的区间上,并且(i - j leq r_j)。当然,我们会贪心地选择这个区间上最左边的合法的(j)。并且,只要某个(j)在当前中心为(i)的时候满足(i - j leq r_j),那么,对于所有(k leq i),都有(k - j leq r_j)

于是,只要从左往右移动(i),每次删除移动后非法的(j),问题就变成实现一个数据结构,支持:

  • 删除一个元素
  • 求一个值后继

这可以轻易地以(O(n log n))的时间复杂度实现,但我们希望一个更优的算法。

考虑一个元素如果每删除了,那么受影响的就只有以它为后继的值。这些值的后继会变成它们后继的后继。设(S_i)表示以元素(i)为后继的所有值的集合,那么,删除(i)后,我们只要把(S_i)合并到(S_j)里面就可以了。(元素(j)是元素(i)的后继)

因此,我们用并查集维护一下就好了。这个巧妙的技巧应该会在本题卡常时发挥作用。

时间复杂度(O(n alpha(n)))

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n,len[N * 2],r[N];
char s[N];
void manacher() {
  for (int i = 2 ; i <= (n << 1) ; ++ i) {
    int l = i >> 1, r = (i + 1) >> 1;
    while (s[l - len[i]] == s[r + len[i]] && l - len[i] >= 1 && r + len[i] <= n)
      ++ len[i];
    l = i - 1, r = i + 1;
    while ((l >> 1) - len[l] > (i >> 1) - len[i] && l >= 1 && r <= 2 * n)
      len[r++] = len[l--];
    len[r] = ((i + 1) >> 1) + len[i] - ((r + 1) >> 1);
    i = r - 1;
  }
}
int uni[N],ans;
vector<int> vec[N];
int getfa(int pos) {
  return pos == uni[pos] ? pos : uni[pos] = getfa(uni[pos]);
}
int main() {
  scanf("%d",&n);
  scanf("%s",s+1);
  manacher();
  for (int i = 1 ; i <= n ; ++ i)
    r[i] = len[i << 1 | 1];
  for (int i = 1 ; i <= n ; ++ i)
    uni[i] = i;
  for (int i = 1 ; i < n ; ++ i) {
    int k = i - (r[i] >> 1);
    ans = max(ans,4 * (i - getfa(k)));
    vec[i + r[i]].push_back(i);
    for (int j = 0 ; j < (int)vec[i].size() ; ++ j)
      uni[vec[i][j]] = vec[i][j] + 1;
  }
  printf("%d
",ans);
  return 0;
}

小结:对一些简单的小技巧还是不熟悉。或许是平庸的大数据结构做多了的问题吧。做题策略要调整。

原文地址:https://www.cnblogs.com/cly-none/p/9379566.html