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\) 就可以得到所有形如:

\[o\cdots o|w\cdots v|o\cdots o \\ w\cdots v|o\cdots o|w\cdots v \]

的回文串。

这个时候,对于每一个这样的回文串,再暴力往两边扩展即可。

时间复杂度 \(\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

成功让图中出现了两个环。

构造的时候发现,这对于字符串的要求非常严格。

不知道如何把这个代码卡到不能过的情况,如果你可以,可以给一个数据分享一下。

或者,你可以直接证明这个解法时间复杂度的正确性,也欢迎分享。

posted @ 2025-11-09 17:23  AxDea  阅读(144)  评论(12)    收藏  举报