CCPC2025哈尔滨站-J. 幻想乡的裁判长
statement
给一个长为 \(n\) 的字符串 \(s\),字符集为 \(\{\text{o, v, w}\}\),请输出最长的回文子串,这个子串中一个 \(\text{w}\) 可以看成两个 \(\text{v}\)。
给个例子:\(\text{wwovvvv}\) 是合法的。
数据范围:多测,\(\sum n\le 10 ^ 7\)。
solution
把字符串看成很多段,连续的 \(o\) 看成一段,连续的 \(v\) 和 \(w\) 也可以看成一段。
这个时候,计算每一段的长度,计算时把 \(w\) 的长度看作 \(2\),其他字符的长度看为 \(1\),按照从左往右的顺序将长度存在一个数组 \(a\) 中。
然后对 \(a\) 进行一个 Manacher,得到数组 \(P\)。
这样通过 \(P\) 就可以得到所有形如:
的回文串。
这个时候,对于每一个这样的回文串,再暴力往两边扩展即可。
时间复杂度 \(\mathcal O(\sum n)\).
代码:(2025.11.13)
int mp[2025];
int n, m, al, ar, a[N], P[N], L[N], R[N]; string s;
inline void init() {
mp['o'] = 1, mp['w'] = mp['v'] = 2;
}
inline void clear() {
al = ar = 1;
rep (i, 0, m) P[i] = L[i] = R[i] = a[i] = 0;
m = 0;
}
inline void upd(int l, int r) {
if (r - l > ar - al) al = l, ar = r;
}
inline void manacher() {
P[0] = 1;
for (int i = 1, j = 0; i < m; ++i) {
if (j + P[j] <= i) P[i] = 0;
else P[i] = min(P[(j << 1) - i], j + P[j] - i);
while (i - P[i] >= 0 && i + P[i] < n && a[i - P[i]] == a[i + P[i]])
P[i] ++ ;
if (i + P[i] > j + P[j]) j = i;
}
}
int g[N];
inline void solve() {
cin >> n >> s;
s = " " + s;
for (int l = 1, r = 1; l <= n; l = r + 1) {
r = l; a[m] = 1 + (s[l] == 'w');
while (r + 1 <= n && mp[s[l]] == mp[s[r + 1]])
a[m] += 1 + (s[++r] == 'w');
L[m] = l, R[m] = r;
m ++ ;
}
manacher();
rep (i, 0, m) {
int u = i - P[i], v = i + P[i];
upd(L[u + 1], R[v - 1]);
if (u < 0 || v >= m) continue ;
int pre = 0, mx = 0;
form (x, R[u], L[u]) pre += 1 + (s[x] == 'w'), g[pre] = x;
mx = pre, pre = 0;
forn (x, L[v], R[v]) {
pre += 1 + (s[x] == 'w');
if (g[pre]) upd(g[pre], x);
}
forn (x, 1, mx) g[x] = 0;
}
forn (i, al, ar) cout << s[i]; cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int z; init();
cin >> z;
while (z -- ) clear(), solve();
return 0;
}
关于上面的代码的复杂度,最坏是 \(\mathcal O (n ^ 2)\)。
为什么呢,直接构造一个 \(ooo\cdots ooooooooooo|owowowowowowowow\cdots\),就能直接卡掉。
如何解决,直接像官解一样 \(\mathcal O(1)\) 找到左右最大的能拓展的点,判断一下即可。
然后对于上述解法,有一个优化,将 pre 变量的上界换为 \(\min\{a_u, a_v\}\),然后再跑一遍。
这样能得到以下代码:
int mp[2025];
int n, m, al, ar, a[N], P[N], L[N], R[N]; string s;
inline void init() {
mp['o'] = 1, mp['w'] = mp['v'] = 2;
}
inline void clear() {
al = ar = 1;
rep (i, 0, m) P[i] = L[i] = R[i] = a[i] = 0;
m = 0;
}
inline void upd(int l, int r) {
if (r - l > ar - al) al = l, ar = r;
}
inline void manacher() {
P[0] = 1;
for (int i = 1, j = 0; i < m; ++i) {
if (j + P[j] <= i) P[i] = 0;
else P[i] = min(P[(j << 1) - i], j + P[j] - i);
while (i - P[i] >= 0 && i + P[i] < n && a[i - P[i]] == a[i + P[i]])
P[i] ++ ;
if (i + P[i] > j + P[j]) j = i;
}
}
int g[N];
inline void solve() {
cin >> n >> s;
s = " " + s;
for (int l = 1, r = 1; l <= n; l = r + 1) {
r = l; a[m] = 1 + (s[l] == 'w');
while (r + 1 <= n && mp[s[l]] == mp[s[r + 1]])
a[m] += 1 + (s[++r] == 'w');
L[m] = l, R[m] = r;
m ++ ;
}
manacher();
rep (i, 0, m) {
int u = i - P[i], v = i + P[i];
upd(L[u + 1], R[v - 1]);
if (u < 0 || v >= m) continue ;
int pre = 0, mx = min(a[u], a[v]);
form (x, R[u], L[u]) {
pre += 1 + (s[x] == 'w');
if (pre > mx) break ;
g[pre] = x;
}
pre = 0;
forn (x, L[v], R[v]) {
pre += 1 + (s[x] == 'w');
if (pre > mx) break ;
if (g[pre]) upd(g[pre], x);
}
forn (x, 1, mx) g[x] = 0;
}
forn (i, al, ar) cout << s[i]; cout << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int z; init();
cin >> z;
while (z -- ) clear(), solve();
return 0;
}
这样的话,只求奇数长度的回文串,构造一个二分图,设左部点为 \(1\) 到 \(n\), 右部点为 \(1\) 到 \(n\),对于manacher求出来的所有长度大于 1 的极长字符串 [l, r],从左部点 l 向右部点 r 连一条无向边,有命题:得出来的二分图中没有大于等于 \(1\) 的环。
若命题成立,那么复杂度是对的。
但是很可惜,命题并不成立,经过 THU 爷 \(\text{myee}\) 和 \(\text{unputdownable}\) 的不懈努力,构造出了一组数据:
1
31
owowovwowowovwowovwowowovwowowo
成功让图中出现了两个环。
构造的时候发现,这对于字符串的要求非常严格。
不知道如何把这个代码卡到不能过的情况,如果你可以,可以给一个数据分享一下。
或者,你可以直接证明这个解法时间复杂度的正确性,也欢迎分享。

浙公网安备 33010602011771号