F-[APIO2014]迴文串
一個板子題, 維護一下每個節點出現次數,倒著把次數往 fail 上推, 長度可以直接維護
#include<bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 3e5 + 100; struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][26], fail[N], len[N], last, tot; // extension int num[N];/*節點代表的所有迴文串出現次數*/ void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = num[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } void add(int ch) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; } last = nxt[cur][ch]; num[last]++; } void build() { //fail[i] < i,拓撲更新可以單調掃描。 for (int i = tot; i >= 2; i--) { num[fail[i]] += num[i]; } num[0] = num[1] = 0; } void init(char* ss) { while (*ss) { add(*ss - 'a'); ss++; } } void init(string str) { for (int i = 0; i < str.size(); i++) { add(str[i] - 'a'); } } void query() { int ans = 1; for (int i = 2; i <= tot; i++) ans = max(ans, len[i] * num[i]); cout << ans << endl; } } PAM; char s[N]; void solve() { int n; cin >> n; cin >> s; PAM.init(s); PAM.build(); PAM.query(); } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; }
G-Colorful String
板子題,開個桶維護一下每個節點字母出現情況就行
#include<bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 3e5 + 100; struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][26], fail[N], len[N], last, tot; // extension int num[N][26], cnt[N]; void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } void add(int ch) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; for (int i = 0; i < 26; i++) num[tt][i] = num[cur][i]; num[tt][ch] = 1; } last = nxt[cur][ch]; cnt[last]++; } void build() { //fail[i] < i,拓撲更新可以單調掃描。 for (int i = tot; i >= 2; i--) cnt[fail[i]] += cnt[i]; cnt[1] = cnt[0] = 1; int ans = 0; for (int i = tot; i >= 2; i--) { int sum = 0; for (int j = 0; j < 26; j++) { if (num[i][j]) sum++; } ans += sum * cnt[i]; } cout << ans << endl; } void init(char* ss) { while (*ss) { add(*ss - 'a'); ss++; } } void init(string str) { for (int i = 0; i < str.size(); i++) { add(str[i] - 'a'); } } } PAM; void solve() { string s; cin >> s; PAM.init(s); PAM.build(); } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; }
P4287 [SHOI2011] 雙倍迴文
引入了 trans 陣列概念, trans 指標指向不超過迴文串長度一半的最長迴文字尾節點
我們對於每個節點檢查是否長度為偶數,trans是否剛好是當前節點代表字串長度一半
#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long const int N = 5e5 + 100; struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][26], fail[N], len[N], tran[N], last, tot; // extension int num[N];/*節點代表的所有迴文串出現次數*/ void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = num[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點 void trans(int x, int y, int pos, int ch) { if (len[x] <= 2) tran[x] = fail[x]; else { int t = tran[y]; while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; tran[x] = nxt[t][ch]; } } void add(int ch, int pos) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; trans(tt, cur, pos, ch); } last = nxt[cur][ch]; } void build() { //fail[i] < i,拓撲更新可以單調掃描。 for (int i = tot; i >= 2; i--) { num[fail[i]] += num[i]; } num[0] = num[1] = 0; } void init(char* str, int n) { for (int i = 1; i <= n; i++) add(str[i] - 'a', i); } void init(string str, int n) { for (int i = 1; i <= n; i++) { add(str[i] - 'a', i); } } // 拓撲遍歷trie樹 void topo() { queue<int> q; for (int i = 0; i < 26; i++) { // 長度為奇數的 if (nxt[1][i]) q.push(nxt[1][i]); // 長度為偶數的 if (nxt[0][i]) q.push(nxt[0][i]); } while (!q.empty()) { int x = q.front(); q.pop(); for (int i = 0; i < 26; i++) { int y = nxt[x][i]; if (!y) continue; q.push(y); } } } void query() { int ans = 0; for (int i = 2; i <= tot; i++) { int x = tran[i]; if (len[x] % 2 == 0 && len[i] == len[x] * 2) ans = max(ans, len[i]); } cout << ans << endl; } } PAM; char s[N]; void solve() { int n; cin >> n; cin >> (s + 1); // string s; cin >> s; // int n = s.size(); // s = " " + s; PAM.init(s, n); PAM.query(); } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T; cin >> T; // while (T--) solve(); solve(); return 0; }
P4762 [CERC2014] Virus synthesis
做法是利用 trans 陣列性質進行 dp
顯然這個串會是一個迴文串 + 左右兩邊加一些字元形式,轉移大概這樣(洛谷題解上的)
#include<bits/stdc++.h> using namespace std; #define endl "\n" #define int long long typedef long long ll; const int N = 1e5 + 100; int f[N]; map<char, int> mp; struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][5], fail[N], len[N], last, tot; // extension int tran[N]; void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點 void trans(int x, int y, int pos, int ch) { if (len[x] <= 2) tran[x] = fail[x]; else { int t = tran[y]; while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; tran[x] = nxt[t][ch]; } } void add(int ch, int pos) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; trans(tt, cur, pos, ch); } last = nxt[cur][ch]; } void build(int n) { //fail[i] < i,拓撲更新可以單調掃描。 for (int i = 2; i <= tot; i++) f[i] = len[i]; queue<int> q; int ans = n; for (int i = 1; i <= 4; i++) { if (nxt[0][i]) q.push(nxt[0][i]); } while (!q.empty()) { int x = q.front(); q.pop(); f[x] = min(f[x], len[x] / 2 - len[tran[x]] + f[tran[x]] + 1); ans = min(ans, f[x] + n - len[x]); for (int i = 1; i <= 4; i++) { int y = nxt[x][i]; if (!y) continue; f[y] = min(f[y], f[x] + 1); q.push(y); } } cout << ans << endl; } void init(string str, int n) { for (int i = 1; i <= n; i++) { add(mp[str[i]], i); } } void init(char* ss, int n) { for (int i = 1; i <= n; i++) add(mp[ss[i]], i); } } PAM; void solve() { string s; cin >> s; int n = s.size(); s = " " + s; mp['A'] = 1; mp['T'] = 2; mp['C'] = 3; mp['G'] = 4; PAM.clear(); PAM.init(s, n); PAM.build(n); } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
P5555 秩序魔咒
簡化下題意大概就是給你兩個字串,找出兩字串所有公共迴文字串中的最大長度, 以及有多少種公共迴文串是最大長度
這種題有兩種做法一種是建一個 PAM,開兩個陣列表示第一個串中是否有這個節點,另外一個就是表示第二個串是否有這個節點
第一個串加完節點後我們把 PAM 中的 now 清空即可, 程式碼比較詳細
第二種做法就是我們建兩個 PAM,一起去跑 dfs,當兩串都有某個節點就繼續 dfs,有個細節是要dfs 0 , 1兩個節點,因為 PAM 有兩個根
這裡給出第一種做法程式碼
#include <bits/stdc++.h> using namespace std; #define endl "\n" #define int long long const int N = 6e6 + 100; int ans, res, vis[N][2]; // 有點逆天, 為什麼空間要開十倍? // 對於兩個串之間的連線,並不需要真的去加一個點(雖然不知道為什麼真的加只能拿95分) // 我們在第一個串 add 完以後把 now clear一下就行, 因為 now 是 PAM 存當前串的狀態的指標 // 我們把它設定為 1 就相當於清空了板子中 s,getfail就不會是找到兩串連線得到的串上面去 struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][30], fail[N], len[N], last, tot; void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } void add(int ch, int op) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; } last = nxt[cur][ch]; vis[last][op] = 1; } } PAM; char s1[N], s2[N]; void solve() { PAM.clear(); int n, m; cin >> n >> m; ans = res = 0; cin >> s1 >> s2; for (int i = 0; i < n; i++) { PAM.add(s1[i] - 'a', 0); } // PAM.add(29, 0); PAM.now = 1; for (int i = 0; i < m; i++) { PAM.add(s2[i] - 'a', 1); } auto &len = PAM.len; for (int i = 0; i <= PAM.tot; i++) { if (vis[i][0] && vis[i][1]) { if (len[i] > ans) ans = len[i], res = 1; else if (len[i] == ans) res++; } } cout << ans << ' ' << res << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); // int T; cin >> T; // while (T--) solve(); solve(); return 0; }
P5685 [JSOI2013] 快樂的 JYY
和上題基本一致,改變一下貢獻計算方式即可
這裡我用第一種做法不知道為什麼一直過不了,改成第二種做法才能透過,所以給出第二種做法程式碼
#include <bits/stdc++.h> using namespace std; #define int long long #define endl "\n" const int N = 5e6 + 100; struct Palindromic_AutoMaton { //basic int s[N], now; int nxt[N][30], fail[N], len[N], tran[N], last, tot; // extension int num[N];/*節點代表的所有迴文串出現次數*/ void clear() { // 1節點 : 奇數長度 root // 0節點 : 偶數長度 root s[0] = len[1] = -1; fail[0] = tot = now = 1; last = len[0] = 0; memset(nxt[0], 0, sizeof nxt[0]); memset(nxt[1], 0, sizeof nxt[1]); } Palindromic_AutoMaton() {clear();} // 建一個新節點, 長度為 nowlen int newnode(int nowlen) { tot++; memset(nxt[tot], 0, sizeof nxt[tot]); fail[tot] = num[tot] = 0; len[tot] = nowlen; return tot; } // 找字尾迴文 int get_fail(int x) { // now - 1 是當前節點 // x 是 s 串中 now - 1 的前一個節點 // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1] // check 出最長迴文字尾 while (s[now - len[x] - 2] != s[now - 1]) x = fail[x]; return x; } // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點 void trans(int x, int y, int pos, int ch) { if (len[x] <= 2) tran[x] = fail[x]; else { int t = tran[y]; while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; tran[x] = nxt[t][ch]; } } void build() { for (int i = tot; i >= 2; i--) { num[fail[i]] += num[i]; } num[0] = num[1] = 0; } void add(int ch, int pos, int op) { s[now++] = ch; int cur = get_fail(last); if (!nxt[cur][ch]) { int tt = newnode(len[cur] + 2); fail[tt] = nxt[get_fail(fail[cur])][ch]; nxt[cur][ch] = tt; trans(tt, cur, pos, ch); } last = nxt[cur][ch]; num[last]++; } void init(char* str, int n, int op) { for (int i = 1; i <= n; i++) add(str[i] - 'A', i, op); } // 拓撲遍歷trie樹 void topo() { queue<int> q; for (int i = 0; i < 26; i++) { // 長度為奇數的 if (nxt[1][i]) q.push(nxt[1][i]); // 長度為偶數的 if (nxt[0][i]) q.push(nxt[0][i]); } while (!q.empty()) { int x = q.front( ); q.pop(); for (int i = 0; i < 26; i++) { int y = nxt[x][i]; if (!y) continue; q.push(y); } } } } P1, P2; char s1[N], s2[N]; int ans; void dfs(int x, int y) { ans += P1.num[x] * P2.num[y]; for (int i = 0; i < 26; i++) { if (P1.nxt[x][i] && P2.nxt[y][i]) dfs(P1.nxt[x][i], P2.nxt[y][i]); } } void solve() { cin >> (s1 + 1) >> (s2 + 1); int n = strlen(s1 + 1), m = strlen(s2 + 1); for (int i = 1; i <= n; i++) P1.add(s1[i] - 'A', i, 0); P1.build(); for (int i = 1; i <= m; i++) P2.add(s2[i] - 'A', i, 1); P2.build(); dfs(0, 0); dfs(1, 1); cout << ans << endl; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); solve(); return 0; }