赛时只有蒋老师和杜老师通过了该题,笔者SA写法也是在cf被卡常了qoj能过。总体来说是一道很优秀的题目,并且有一定的思考和代码难度。
注意到从前往后考虑 $ S $ 串不容易维护。我们考虑从最后一个状态出发,最后的 $ S $ 串一定存在是一个字符的情况。然后我们构成出反串 $ S^R $ ,上一个状态可能是由 $ t S w S ^ R t ^ R $ 组成,这里有两种情况。
- 所有情况,那么贪心考虑, \(t\) 和 \(t^R\) 一定不存在。我们只需要寻找最靠前的 \(S^R\) 即可,左边界不变(\(w\) 可能为空)。
- 若 $ S $ 是回文串且 $ S $前面一个字符和后面一个字符相等,那么 \(t S t\) 依旧是回文串,左边界左移1位。若$ t S $ 依旧是回文串,左边界左移1位。
注意到操作1的 \(w\) 串为空时,新串为回文串,否则 \(S\) 的长度至少翻倍。那么操作1在 \(w\) 不为空的操作次数最多执行 \(log\) 次。
注意到对于回文子串 $ T $ 可以写成 $ t w t^R $,在 \(t\) 中选择的左端点进行操作1,一定能匹配到 \(t^R\) 中的部分。若再执行操作2,一定劣于从 \(w\) 开始执行操作2,且一定能执行出相同的左右端点。这里显然发现对于最多的操作次数一定为形如 \(222...2111...1\) 。因为若操作1形成回文串,不如直接从回文中心开始构造。
再次注意到,对于回文串,考虑从回文串最左边开始匹配进行操作1,一定是匹配 \(w\) 为空的新状态,即对于操作结束后相同的左端点可以执行操作2那么一定执行操作2。
那么我们用回文树/马拉车先维护出所有左端点开始操作最多的回文串。再暴力执行1操作,考虑次数即可。
这里提一下操作1的实现。可以用SA+二分找到\(t^R\)的\(rk\)范围。然后用主席树二分找大于这个位置的最小值。但是这个写法常数比较大,在cf被卡常了。我们可以用SAM或者exSAM,然后使用线段树合并,在线二维数点等方法找到大于这个位置的最小值。
时间复杂度都是\(O(nlognlogn)\)
code SA + 二分主席树
#include <bits/stdc++.h>
#define ll long long
#define enl putchar('\n')
#define all(x) (x).begin(),(x).end()
#define debug(x) printf(" "#x":%d\n",x);
using namespace std;
const int MAXN = 2e5 + 5, LOG = 19;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
typedef pair<int, int> pii;
char buf[1 << 21], * p1 = buf, * p2 = buf, obuf[1 << 21], * o = obuf, of[35];
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline ll qpow(ll a, ll n) { ll res = 1; while (n) { if (n & 1)res = res * a % mod; n >>= 1; a = a * a % mod; }return res; }
template <class T = int>inline T read() { T s = 0, f = 1; char c = gc(); for (; !isdigit(c); c = gc())if (c == '-')f = -1; for (; isdigit(c); c = gc())s = s * 10 + c - '0'; return s * f; }
inline void read(int* a, int n) { for (int i = 1; i <= n; ++i)a[i] = read(); }
inline int inal(char* s) { int n = 0; for (s[0] = gc(); !isalpha(s[0]); s[0] = gc()); for (; isalpha(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline int indi(char* s) { int n = 0; for (s[0] = gc(); !isdigit(s[0]); s[0] = gc()); for (; isdigit(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline void outd(auto* a, int n) { for (int i = 1; i <= n; ++i)printf("%d ", a[i]); enl; }
int n, m, q;
char s[MAXN], str[MAXN];
int f[MAXN], com[MAXN];
struct HJT {
int T[MAXN], sum[MAXN * LOG], L[MAXN * LOG], R[MAXN * LOG];
int tot, n;
void clear(int n) {
memset(T + 1, 0, sizeof(int) * n);
tot = 0;
}
void insert(int l, int r, int p, int& u, const int& tar) {
sum[++tot] = sum[p] + 1;
L[tot] = L[p]; R[tot] = R[p];
u = tot;
if (l == r)return;
int m = (l + r) >> 1;
if (tar > m)insert(m + 1, r, R[p], R[u], tar);
else insert(l, m, L[p], L[u], tar);
}
int query(const int& l, const int& r, const int& qv, const int& u, const int& p) {
if (l == r)return l;
int m = (l + r) >> 1, res = 0;
int lv = sum[L[u]] - sum[L[p]], rv = sum[R[u]] - sum[R[p]];
if (qv <= m && lv)res = query(l, m, qv, L[u], L[p]);
if (!res && rv && r != n)res = query(m + 1, r, qv, R[u], R[p]);
return res;
}
};
struct Sa { //俩字符串连一起记得开俩倍空间
char s[MAXN];
int SA[MAXN], rk[MAXN], oldrk[MAXN << 1], id[MAXN], key1[MAXN], cnt[MAXN]; //SA排名为i对应原字符串下标,rk下标为i的后缀排名
int height[MAXN], n; //height与上一个排名相同的个数,height[1]=0
HJT hjt;
void clear() {
hjt.clear(n);
memset(cnt, 0, sizeof(int) * max(n + 5, 128));
memset(rk + 1, 0, sizeof(int) * n);
}
inline bool cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}
inline void getSA() {
int m = 127;
for (int i = 1; i <= n; ++i)++cnt[rk[i] = s[i]];
for (int i = 1; i <= m; ++i)cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i)SA[cnt[rk[i]]--] = i;
for (int len = 1, p;; len <<= 1, m = p) {
p = 0;
for (int i = n; i > n - len; --i)id[++p] = i;
for (int i = 1; i <= n; ++i)
if (SA[i] > len)id[++p] = SA[i] - len;
memset(cnt + 1, 0, sizeof(int) * m);
for (int i = 1; i <= n; ++i)++cnt[key1[i] = rk[id[i]]];
for (int i = 1; i <= m; ++i)cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i)SA[cnt[key1[i]]--] = id[i];
memcpy(oldrk + 1, rk + 1, n * sizeof(int));
p = 0;
for (int i = 1; i <= n; ++i)
rk[SA[i]] = cmp(SA[i], SA[i - 1], len) ? p : ++p;
if (p == n)break;
}
hjt.n = n;
for (int i = 1; i <= n; ++i)
hjt.insert(1, n, hjt.T[i - 1], hjt.T[i], SA[i]);
}
inline void getHeight() {
for (int i = 1, k = 0; i <= n; ++i) {
if (rk[i] == 0)continue;
if (k)--k;
while (s[i + k] == s[SA[rk[i] - 1] + k])++k;
height[rk[i]] = k;
}
}
int st[MAXN][LOG], lg[MAXN];
inline void initST() {
lg[1] = 0;
for (int i = 2; i <= n; i++)lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= n; i++)st[i][0] = height[i];
for (int j = 1; j <= lg[n]; j++)
for (int i = 1; i <= n - (1 << j) + 1; i++)
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
inline int RMQ(int x, int y) {
if (x > y)return 0;
int t = lg[y - x + 1];
return min(st[x][t], st[y - (1 << t) + 1][t]);
}
inline int LCP(int x, int y) {
x = rk[x], y = rk[y];
if (x > y)swap(x, y);
return RMQ(x + 1, y);
}
int findL(int p, int len) {
int l = 1, r = p;
while (l < r) {
int mid = l + r >> 1;
if (RMQ(mid + 1, p) >= len)r = mid;
else l = mid + 1;
}
return r;
}
int findR(int p, int len) {
int l = p, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (RMQ(p + 1, mid) >= len)l = mid;
else r = mid - 1;
}
return l;
}
int find_nxt(int l, int r) {
int len = r - l + 1;
int L = findL(rk[n - r + 1], len), R = findR(rk[n - r + 1], len);
int res = hjt.query(1, n, r + 1, hjt.T[L - 1], hjt.T[R]);
return (res && res <= n / 2) ? res + len - 1 : 0;
}
}p;
void manacher(char* str, int n) {
int t = 0;
for (int i = 1; i <= n; ++i) {
if (t + f[t] >= i)f[i] = min(f[t - (i - t)], t + f[t] - i);
while (i - f[i] - 1 > 0 && i + f[i] + 1 <= n && str[i - f[i] - 1] == str[i + f[i] + 1])f[i]++;
if (f[i] + i > f[t] + t) t = i;
}
}
vector<array<int, 3>> op[MAXN];
void solve() {
n = inal(s + 1);
memcpy(p.s + 1, s + 1, sizeof(char) * n);
p.s[n + 1] = '#';
for (int i = 1; i <= n; ++i) p.s[n + i + 1] = s[n - i + 1];
p.n = n + n + 1;
p.clear();
p.getSA();
p.getHeight();
p.initST();
int ans = 0;
for (int i = 1; i <= n; ++i) op[i].clear();
for (int i = n; i >= 1; --i)
com[i] = s[i] == s[i + 1] ? com[i + 1] + 1 : 0;
str[m = 1] = '#';
for (int i = 1; i <= n; ++i)
str[++m] = s[i], str[++m] = '#';
memset(f + 1, 0, sizeof(int) * m);
manacher(str, m);
for (int i = 1; i <= m; ++i) {
int l = (i - f[i] + 1) / 2, c = (i + 1) / 2, r = (i + f[i]) / 2;
int f = i & 1 ? -1 : 1;
int v = min(c + com[c], r);
op[l].push_back({v, f * c, 1});
op[c].push_back({v, f * c, 0});
}
set<pii>S;
for (int i = 1; i <= n; ++i) {
for (auto x : op[i]) {
if (x[2])S.insert({x[0], x[1]});
else S.erase(S.find({x[0], x[1]}));
}
int v = 0, c = i;
if (S.size()) {
auto it = *S.rbegin();
v = it.first - i;
if (it.second > 0) c = it.second + it.second - i;
else c = -it.second - it.second - i - 1;
c = max(c, it.first);
}
while (c = p.find_nxt(i, c))
++v;
ans = max(ans, v);
}
printf("%d\n", ans);
memset(p.s + 1, 0, sizeof(char) * m);
}
signed main(signed argc, char const* argv[]) {
clock_t c1 = clock();
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
//=============================================================
int TxT = 1;
TxT = read();
while (TxT--)
solve();
//=============================================================
#ifdef LOCAL
end :
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
#endif
return 0;
}
code exSAM + 线段树合并
#include <bits/stdc++.h>
#define ll long long
#define enl putchar('\n')
#define all(x) (x).begin(),(x).end()
#define debug(x) printf(" "#x":%d\n",x);
using namespace std;
const int MAXN = 2e5 + 5, LOG = 20;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
typedef pair<int, int> pii;
char buf[1 << 21], * p1 = buf, * p2 = buf, obuf[1 << 21], * o = obuf, of[35];
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline ll qpow(ll a, ll n) { ll res = 1; while (n) { if (n & 1)res = res * a % mod; n >>= 1; a = a * a % mod; }return res; }
template <class T = int>inline T read() { T s = 0, f = 1; char c = gc(); for (; !isdigit(c); c = gc())if (c == '-')f = -1; for (; isdigit(c); c = gc())s = s * 10 + c - '0'; return s * f; }
inline void read(int* a, int n) { for (int i = 1; i <= n; ++i)a[i] = read(); }
inline int inal(char* s) { int n = 0; for (s[0] = gc(); !isalpha(s[0]); s[0] = gc()); for (; isalpha(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline int indi(char* s) { int n = 0; for (s[0] = gc(); !isdigit(s[0]); s[0] = gc()); for (; isdigit(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline void outd(auto* a, int n) { for (int i = 1; i <= n; ++i)printf("%d ", a[i]); enl; }
int n, m, q;
char s[MAXN], str[MAXN];
int f[MAXN], com[MAXN];
struct SGT {
int L[MAXN * LOG * 8], R[MAXN * LOG * 8], v[MAXN * LOG * 8];
int T[MAXN << 1], tot;
void insert(int p, int& x, int l = 1, int r = n) {
if (!x)x = ++tot;
v[x]++;
if (l == r) return;
int mid = l + r >> 1;
if (p <= mid)insert(p, L[x], l, mid);
else insert(p, R[x], mid + 1, r);
}
void merge(int& x, int y, int l = 1, int r = n) {
// if (!x || !y) { x |= y; return; }
if (!y)return;
if (!x) { x = ++tot; v[x] = v[y]; L[x] = L[y], R[x] = R[y]; return; }
v[++tot] = v[y] + v[x];
L[tot] = L[x];
R[tot] = R[x];
x = tot;
if (l == r) return;
int mid = l + r >> 1;
merge(L[x], L[y], l, mid);
merge(R[x], R[y], mid + 1, r);
v[x] = v[L[x]] + v[R[x]];
}
int query(int x, const int& qv, int l = 1, int r = n) {
if (l == r)return l;
int m = (l + r) >> 1, res = 0;
if (qv <= m && v[L[x]])res = query(L[x], qv, l, m);
if (!res && v[R[x]])res = query(R[x], qv, m + 1, r);
return res;
}
};
struct exSAM : SGT { //最多2n-1个点和3n-4条边
int len[MAXN << 1], link[MAXN << 1], ch[MAXN << 1][26]; //我们记 longest(v) 为其中最长的一个字符串,记 len(v) 为它的长度。
int pos[MAXN << 1], rp[MAXN << 1];
vector<int> G[MAXN << 1];
int cur, lst, siz;
exSAM() { clear(); }
void clear() { //设置起始点S
for (int i = 0; i <= siz; ++i) G[i].clear();
memset(ch, 0, sizeof(int) * (siz + 1) * 26);
memset(pos, 0, sizeof(int) * (siz + 1));
memset(rp, 0, sizeof(int) * (siz + 1));
memset(T, 0, sizeof(int) * (siz + 1));
memset(L, 0, sizeof(int) * (tot + 1));
memset(R, 0, sizeof(int) * (tot + 1));
memset(v, 0, sizeof(int) * (tot + 1));
tot = 0;
len[0] = 0;
link[0] = -1;
siz = 0; //siz设置成0实际上有一个点,方便标记
lst = cur = 0;
}
int extend(int c) {
lst = cur;
if (ch[lst][c]) {
int q = ch[lst][c];
if (len[lst] + 1 == len[q])return cur = q;
else {
int clone = ++siz;
link[clone] = link[q];
len[clone] = len[lst] + 1;
link[q] = clone;
for (; ~lst && ch[lst][c] == q; lst = link[lst])ch[lst][c] = clone;
memcpy(ch[clone], ch[q], sizeof(ch[q]));
return cur = clone;
}
}
cur = ++siz;
len[cur] = len[lst] + 1;
for (; ~lst && !ch[lst][c]; lst = link[lst])ch[lst][c] = cur;
if (lst == -1) {
link[cur] = 0;
} else {
int q = ch[lst][c];
if (len[lst] + 1 == len[q]) {
link[cur] = q;
} else { //克隆的点是q(lst的c后继)
int clone = ++siz;
link[clone] = link[q];
len[clone] = len[lst] + 1;
link[cur] = link[q] = clone;
for (; ~lst && ch[lst][c] == q; lst = link[lst])ch[lst][c] = clone;
memcpy(ch[clone], ch[q], sizeof(ch[q]));
}
}
return cur;
}
int fa[MAXN << 1][LOG];
void dfs(int x) {
for (int i = 1; i < LOG; ++i)
fa[x][i] = fa[fa[x][i - 1]][i - 1];
if (pos[x])insert(pos[x], T[x]);
for (auto v : G[x]) {
dfs(v);
merge(T[x], T[v]);
}
}
void build() {
for (int i = 1; i <= siz; ++i) {
G[link[i]].push_back(i);
fa[i][0] = link[i];
}
dfs(0);
}
int FindR(int l, int r) {
int x = rp[l];
for (int i = LOG - 1; i >= 0; --i)
if (len[fa[x][i]] >= r - l + 1)
x = fa[x][i];
return x;
}
int find_nxt(int l, int r) {
if (r == n)return 0;
int x = FindR(l, r);
return query(T[x], r + r - l + 1);
}
}p;
void manacher(char* str, int n) {
int t = 0;
for (int i = 1; i <= n; ++i) {
if (t + f[t] >= i)f[i] = min(f[t - (i - t)], t + f[t] - i);
while (i - f[i] - 1 > 0 && i + f[i] + 1 <= n && str[i - f[i] - 1] == str[i + f[i] + 1])f[i]++;
if (f[i] + i > f[t] + t) t = i;
}
}
vector<array<int, 3>> op[MAXN];
void solve() {
p.clear();
n = inal(s + 1);
for (int i = 1; i <= n; ++i) {
int c = p.extend(s[i] - 'a');
p.pos[c] = i;
}
p.cur = 0;
for (int i = n; i >= 1; --i) {
int c = p.extend(s[i] - 'a');
p.rp[i] = c;
}
p.build();
int ans = 0;
for (int i = 1; i <= n; ++i) op[i].clear();
for (int i = n; i >= 1; --i)
com[i] = s[i] == s[i + 1] ? com[i + 1] + 1 : 0;
str[m = 1] = '#';
for (int i = 1; i <= n; ++i)
str[++m] = s[i], str[++m] = '#';
memset(f + 1, 0, sizeof(int) * m);
manacher(str, m);
for (int i = 1; i <= m; ++i) {
int l = (i - f[i] + 1) / 2, c = (i + 1) / 2, r = (i + f[i]) / 2;
int f = i & 1 ? -1 : 1;
int v = min(c + com[c], r);
op[l].push_back({v, f * c, 1});
op[c].push_back({v, f * c, 0});
}
set<pii>S;
for (int i = 1; i <= n; ++i) {
for (auto x : op[i]) {
if (x[2])S.insert({x[0], x[1]});
else S.erase(S.find({x[0], x[1]}));
}
int v = 0, c = i;
if (S.size()) {
auto it = *S.rbegin();
v = it.first - i;
if (it.second > 0) c = it.second + it.second - i;
else c = -it.second - it.second - i - 1;
c = max(c, it.first);
}
while (c = p.find_nxt(i, c))
++v;
ans = max(ans, v);
}
printf("%d\n", ans);
}
signed main(signed argc, char const* argv[]) {
clock_t c1 = clock();
#ifdef LOCAL
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
//=============================================================
int TxT = 1;
TxT = read();
while (TxT--)
solve();
//=============================================================
#ifdef LOCAL
end :
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
#endif
return 0;
}