PAM
一个板子题, 维护一下每个节点出现次数,倒着把次数往 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; }
板子题,开个桶维护一下每个节点字母出现情况就行
#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; }
引入了 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; }
简化下题意大概就是给你两个字符串,找出两字符串所有公共回文字符串中的最大长度, 以及有多少种公共回文串是最大长度
这种题有两种做法一种是建一个 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; }
和上题基本一致,改变一下贡献计算方式即可
这里我用第一种做法不知道为什么一直过不了,改成第二种做法才能通过,所以给出第二种做法代码
#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; }