JZOJ 4366. 【GDKOI2016】項鍊
阿新 • • 發佈:2021-07-11
\(\text{Problem}\)
給出一個項鍊,刪去連續的一部分,使剩下的對稱,且長度最長
\(\text{Analysis}\)
可以發現,剩下的合法項鍊一定是由兩個迴文串接起來(由對稱性質可知)
先套路地將項鍊倍長
那麼我們只要用 \(\text{Manacher}\) 算出以每個點中心的迴文半徑 \(p[i]\)
找到兩個中心 \(i,j(j > i)\) 使得 \(j - i \le n\) 且 \(i + p[i] - 1 \le j - p[j] + 1\)
\(n\) 為讀入的串的長度
列舉一個 \(i\),動態確定一個決策集合,線段樹維護最優解即可
注意一個細節,線段樹維護的時候一個位置可能加入多個值,這些都是可行的,但刪除的時候會使所有的值消失,需要特別處理
\(O(n \log n)\)
但還有掃描線+並查集 \(o(n \alpha(n))\) 的優秀解法(不會)
#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int N = 2e5 + 5; int p[N << 1], n, len, bz[N << 1]; char s[N], str[N << 1]; void Manacher() { str[0] = '@', str[++n] = '#'; scanf("%s", s); len = strlen(s); for(register int i = 0; i < len; i++) str[++n] = s[i], str[++n] = '#'; for(register int i = 0; i < len; i++) str[++n] = s[i], str[++n] = '#'; str[++n] = '\0', len = len * 2; int mx = 0, id = 0; for(register int i = 1; i <= n; i++) { p[i] = (i < mx ? min(p[id * 2 - i], mx - i) : 1); while (i - p[i] + len > i + p[i] && str[i + p[i]] == str[i - p[i]]) ++p[i]; if (i + p[i] > mx) mx = i + p[i], id = i; } } #define ls (p << 1) #define rs (ls | 1) int seg[N * 8]; void update(int p, int l, int r, int x, int v) { if (l == r) { bz[v] = 1, seg[p] = v; return; } int mid = (l + r) >> 1; if (x <= mid) update(ls, l, mid, x, v); else update(rs, mid + 1, r, x, v); seg[p] = max(seg[ls], seg[rs]); } int query(int p, int l, int r, int x, int y) { if (x <= l && r <= y) return seg[p]; int mid = (l + r) >> 1, res = 0; if (x <= mid) res = query(ls, l, mid, x, y); if (y > mid) res = max(res, query(rs, mid + 1, r, x, y)); return res; } int Solve() { int ans = 0; len >>= 1; for(register int i = 1; i <= len + 1; i++) update(1, 0, n, i - p[i] + 1, i); for(register int i = 1; i <= n - len; i++) { ans = max(ans, query(1, 0, n, 0, i + p[i] - 1) - i); if (!bz[i]) update(1, 0, n, i - p[i + 1] + 1, 0); update(1, 0, n, i + 1 + len - p[i + 1 + len] + 1, i + 1 + len); } return ans; } int main() { Manacher(); printf("%d\n", Solve()); }