[題解] Luogu P5446 [THUPC2018]綠綠和串串
阿新 • • 發佈:2021-08-29
[題解] Luogu P5446 [THUPC2018]綠綠和串串
·題目大意
- 定義一個翻轉操作\(f(S_n)\),表示對於一個字串\(S_n\),
有\(f(S)= \{S_1,S_2,...,S_{n-1},S_n,S_{n-1},...S_2,S_1 \}\)。 - 現在給定一個長度為\(n\)的字串\(S^{'}\)表示原字串\(S\)經過若干次(可能為0)旋轉之後的一個字首,
求原來字串可能的長度\(l\)。 - 顯然當\(l > n\)時一定可行,所以只需要輸出所有的\(l\leq n\)即可。
\(|S|\leq 10^6,\Sigma |S| \leq 5 \times 10^6\)
·解題思路
首先想到用 \(Manacher\) 。
由於進行翻轉操作後迴文串長度必定為奇數,所以不用插入字元,然後考慮什麼情況下長度是可行的。
- 我們定義一個 \(flag\) 陣列,\(flag[i]\) 表示長度為 \(i\) 時是可行的。迴文陣列為\(p\),\(p[i]\)表示第 \(i\) 位的迴文半徑位 \(p[i]\)
- 如果只進行了一次翻轉操作即可使得字首為\(S^{'}\),那麼有 \(i + p[i] - 1 == n\)
- 如果需要進行\(k\)次翻轉才可以使得字首為\(S^{'}\),那麼有 \(i - p[i] + 1 == 1\),然後可以轉化為進行\(k - 1\)
但是實際操作中我們不用跑 \(k\) 次,只需要倒著跑並記錄 \(flag\) ,因為當我們處理長度為 \(i\) 的時候,\(flag[i + 1]\) 到 \(flag[n]\) 都已經處理過了,所以判斷 \(flag[i + p[i] - 1] == 1\)即可。 - 時間複雜度為\(O(n)\)。
程式碼實現
#include <iostream> #include <cstring> using namespace std; #define reg register namespace io { char ch[20]; template<typename T>inline void write(T x) { (x < 0) && (x =- x, putchar('-')); (x) || putchar('0'); reg int i = 0; while (x) ch[i++] = x % 10 ^48, x /= 10; while (i) putchar(ch[--i]); } }//快寫 #define wt io::write const int maxN = 1000010; char s[maxN]; int p[maxN], flag[maxN]; int n; void work(); int main() { int t; scanf("%d", &t); while (t--) work(); return 0; } void work() { for (reg int i = 1; i <= n; ++i) flag[i] = p[i] = s[i] = 0; n = 1; s[0] = '@'; scanf("%s", s + 1); while (s[n]) ++n; --n; for (reg int i = 1, r = 0, mid = 0; i <= n; ++i) { if (i <= r) p[i] = min(p[mid * 2 - i], r - i + 1); while (s[i + p[i]] == s[i - p[i]]) ++p[i]; if (i + p[i] - 1 >= r) r = i + p[i] - 1, mid = i; }//Manacher for (reg int i = n; i; --i) { if (i + p[i] - 1 == n || (flag[i + p[i] - 1] && i - p[i] + 1 == 1)) flag[i] = 1; }//上面說的兩種情況 for (reg int i = 1; i <= n; ++i) if (flag[i]) wt(i), putchar(' '); putchar('\n'); }