回文自动机 PAM
https://oi-wiki.org/string/pam/#_4
临时口胡
2014 Asia Xian Regional Contest G
给出两个字符串A,B。求A的所有回文串在B中出现次数的和。
可能是对两个树搜索公共部分···
2019牛客暑期多校训练营(第六场)C:Palindrome Mouse(回文树+树剖)
https://blog.youkuaiyun.com/cymbals/article/details/98398375
给定字符串Str,求出回文串集合为S,问S中的(a,b)满足a是b的子串的对数。
https://ac.nowcoder.com/acm/contest/886/C
我感觉,或许和链深度有关。
luogu 最长双回文串
输入长度为nnn的串SSS,求SSS的最长双回文子串TTT,即可将TTT分为两部分XXX,YYY,(∣X∣,∣Y∣≥1|X|,|Y|≥1∣X∣,∣Y∣≥1)且XXX和YYY都是回文串。
我觉得是排除掉直接fail连向根的节点去找。
大致意思就是让你求出一个字符串中所有的奇回文串,并把它们的长度前k的长度连乘
由于答案可能很大,输出除以19930726的余数。
马拉车也可做,但是回文树奇根和偶根是独立的,也可以操作的样子。
(但我有奇偶分离的马拉车,其实还好。
BestCoder Round #52 (div.1)
给你n次操作,
如果为1,则在字符串后面插入一个字符,
如果为2,则在字符串前面插入一个字符,
如果为3,则输出当前的字符串中的本质不同的回文串的个数,
如果为4,则输出字符串的回文串的个数。
看起来要双向维护pam,这个变形比较需要基本原理,有点难度。
细枝末节的考虑
struct 还是 namespace 这是个问题。
两者各有各的好处。
namespace 的优势在于,如果你的逻辑都在一个模板里改,那么会很方便,但是在外面需要敲范围解析运算符::
。而且在某些情况下,会避免初始化的问题。
struct 的优势在于,要根外部逻辑结合的时候,就一个点符号是比较方便的。而且容易让人去意识到作用域的问题。
我觉得两种方法都有可取的地方。
现场赛的时候甚至可能要直接敲成全局(如果是裸题的话)
2019 ICPC 徐州站 网络赛 G (unarchieved)
https://nanti.jisuanke.com/t/41389
题意
设一个回文串的贡献为其包含的不同字符的个数,则给出一个原串,求其所有回文子串的贡献之和。
思路
知道PAM可以遍历所有的回文子串,那么只要得知每个回文子串的位置即可。
这个信息很好维护。
至于求权值可以借助前缀做。
代码
namespace版本
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;
namespace PAM {
int tot, las;
int ch[PAMN][26];
int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
cnt[tot] = 0;
fail[tot] = 0;
npos[tot] = pos; // 标记回文串第次出现的位置
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '$......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c) {
s[++pos] = c;
int d = c - 'a';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
npos[ch[u][d]] = pos; // 尝试表示每个回文串最后的位置。(删掉也对)
las = ch[u][d];
cnt[las]++;
}
int arr[PAMN][26];
ll solve() {
ll ans = 0;
for (int i = tot; i > 1; i--) {
cnt[fail[i]] += cnt[i];
}
for (int i = 1; i <= pos; ++i) {
memcpy(arr[i], arr[i - 1], sizeof arr[0]);
arr[i][s[i] - 'a']++;
}
for (int i = 2, l, r; i <= tot; i++) {
ll tmp = 0;
r = npos[i];
l = r - len[i];
for (int j = 0; j < 26; ++j) {
tmp += ll(arr[l][j] < arr[r][j]);
}
ans += tmp * cnt[i];
}
return ans;
}
}; // namespace pam
char s[PAMN];
int main() {
PAM::init();
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
PAM::extend(s[i]);
}
printf("%lld\n", PAM::solve());
return 0;
}
struct版本
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;
struct PAM {
int tot, las;
int ch[PAMN][26];
int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
cnt[tot] = 0;
fail[tot] = 0;
npos[tot] = pos;
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '$......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c) {
s[++pos] = c;
int d = c - 'a';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
las = ch[u][d];
cnt[las]++;
}
} pam;
char s[PAMN];
int arr[PAMN][26];
int main() {
pam.init();
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
pam.extend(s[i]);
}
ll ans = 0;
for (int i = pam.tot; i > 1; i--) {
pam.cnt[pam.fail[i]] += pam.cnt[i];
}
for (int i = 1; i <= n; ++i) {
memcpy(arr[i], arr[i - 1], sizeof arr[0]);
arr[i][s[i] - 'a']++;
}
for (int i = 2, l, r; i <= pam.tot; i++) {
ll tmp = 0;
r = pam.npos[i];
l = r - pam.len[i];
for (int j = 0; j < 26; ++j) {
tmp += ll(arr[l][j] < arr[r][j]);
}
ans += tmp * pam.cnt[i];
}
printf("%lld\n", ans);
return 0;
}
2018 ICPC 南京站 网络赛 I Skr
https://nanti.jisuanke.com/t/A1955
题意
长度为n的数字串,只包含数字1~9,求所有本质不同的回文串代表的整数之和
思路
维护前缀数字的值,PAM枚举所有回文区间,乘上系数减一下就可以了。
代码
/**
* @Source: myself
* @Author: Tieway59
* @Complexity: $O(|S|)$
* @Description:
* 长度为n的数字串,只包含数字1~9,求所有本质不同的回文串代表的整数之和
* MOD 1e9+7
*
* @Example:
* "1232111":ans = 1+11+111+2+3+232+12321 = 12681
*
* @Verification:
* 2018 ICPC 南京站 网络赛 I Skr
* https://nanti.jisuanke.com/t/A1955
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 2e6 + 59;
struct PAM {
int tot, las;
int ch[PAMN][26];
// int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
// cnt[tot] = 0;
fail[tot] = 0;
npos[tot] = pos;
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '$......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c) {
s[++pos] = c;
int d = c - '0';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
las = ch[u][d];
// cnt[las]++;
}
} pam;
char s[PAMN];
ll pre[PAMN];
const ll MOD = 1e9 + 7;
ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int main() {
pam.init();
scanf("%s", s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
pam.extend(s[i]);
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
pre[i] = (pre[i - 1] * 10 + s[i] - '0') % MOD;
}
for (int i = 2; i <= pam.tot; ++i) {
int len = pam.len[i];
int r = pam.npos[i];
int l = r - len;
ans = (ans + pre[r] + MOD - pre[l] * fpow(10, len) % MOD) % MOD;
}
printf("%lld\n", ans);
return 0;
}
2020 ICPC UNCPC H
题意
统计一个串长度大于1并且是奇数的本质不同回文子串个数。
思路
就枚举所有状态即可。
代码
/**
* @Source: myself
* @Author: Tieway59
* @Complexity: $O(N)$
* @Description:
* 统计一个串长度大于1并且是奇数的本质不同回文子串个数。
*
* @Example:
* 5
* ababa
*
* ans = 3
*
* @Verification:
* 2020 ICPC UNCPC H. Happy game
* https://codeforces.ml/gym/102700/problem/H
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 1e5 + 59;
struct PAM {
int tot, las;
int ch[PAMN][26];
// int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
// int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
// cnt[tot] = 0;
fail[tot] = 0;
// npos[tot] = pos;
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '$......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c) {
s[++pos] = c;
int d = c - 'a';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
las = ch[u][d];
// cnt[las]++;
}
} pam;
char s[PAMN];
ll pre[PAMN];
const ll MOD = 1e9 + 7;
ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int main() {
pam.init();
int n;
scanf("%d %s", &n, s + 1);
for (int i = 1; i <= n; i++) {
pam.extend(s[i]);
}
ll ans = 0;
for (int i = 2; i <= pam.tot; ++i) {
if (pam.len[i] > 1)
ans += pam.len[i] % 2;
}
printf("%lld\n", ans);
return 0;
}
2019 杭电多校2 1009
/**
* @Source:myself
* @Author: Tieway59
* @Complexity: $O(N)$
* @Description:
* 给出一个长度为N的字符串,要求输出一个长度为N的统计数组A
* A[i]表示长度为i的good substring的数量
* good substring :该子串是回文串,且该子串的一半也是回文串。
* 做法是子串哈希,PAM枚举所有子串,检查。
*
* @Example:
* abababa
* 7 0 0 0 3 0 0
*
* @Verification:
* 2019 杭电多校2 1009 I Love Palindrome String
* http://acm.hdu.edu.cn/showproblem.php?pid=6599
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;
struct PAM {
int tot, las;
int ch[PAMN][26];
int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
cnt[tot] = 0;
fail[tot] = 0;
npos[tot] = pos; //记录第一次出现的位置
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '¥......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c) {
s[++pos] = c;
int d = c - 'a';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
las = ch[u][d];
cnt[las]++;
// npos[las] = pos; // 记录最后出现的位置
}
} pam;
char s[PAMN];
typedef long long ll;
const ll MOD = 1e9 + 7;
ll pre[PAMN];
ll ans[PAMN];
ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
ll subhush(int l, int r) {
l--;
return (pre[r] + MOD - pre[l] * fpow(19491001, r - l) % MOD) % MOD;
}
int main() {
while (scanf("%s", s + 1) != EOF) {
int n = strlen(s + 1);
pam.init();
for (int i = 1; i <= n; i++) {
pam.extend(s[i]);
ans[i] = 0;
pre[i] = 0;
}
for (int i = 1; i <= n; ++i) {
pre[i] = (pre[i - 1] * 19491001 + (s[i] - 'a')) % MOD;
}
for (int i = pam.tot; i > 1; --i) {
pam.cnt[pam.fail[i]] += pam.cnt[i];
}
for (int i = 2; i <= pam.tot; ++i) {
int r = pam.npos[i];
int l = r - pam.len[i] + 1;
int len = pam.len[i];
int m = (l + r) >> 1;
if (len & 1) {
if (subhush(l, m) == subhush(m, r)) {
ans[len] += pam.cnt[i];
}
} else {
if (subhush(l, m) == subhush(m + 1, r)) {
ans[len] += pam.cnt[i];
}
}
}
for (int i = 1; i <= n; ++i) {
printf("%lld%c", ans[i], " \n"[i == n]);
}
}
return 0;
}
洛谷 P4555 [国家集训队]最长双回文串
/**
* @Source:myself
* @Author: Tieway59
* @Complexity: $O(N)$
* @Description:
* 输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,
* ∣X∣,∣Y∣≥1|X|,|Y|≥1∣X∣,∣Y∣≥1 且X和Y都是回文串。
*
* 转化成枚举后缀拼接就很好做了。
* 思路是用PAM处理每个位置i的最长回文后缀,lb[i] (反向是rb[i])
* $ans = max(lb[i] + rb[i + 1])$
*
* @Example:
* baacaabbacabb
* ans := 12
*
* @Verification:
* 洛谷 P4555 [国家集训队]最长双回文串
* https://www.luogu.com.cn/problem/P4555
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int PAMN = 1.2e5 + 59;
struct PAM {
int tot, las;
int ch[PAMN][26];
int cnt[PAMN];
int len[PAMN];
int fail[PAMN];
// int npos[PAMN];
int pos;
char s[PAMN];
int node(int l) {
tot++;
memset(ch[tot], 0, sizeof(ch[tot]));
len[tot] = l;
cnt[tot] = 0;
fail[tot] = 0;
// npos[tot] = pos; //记录第一次出现的位置
return tot;
}
void init() {
tot = -1;
las = 0;
pos = 0;
s[pos] = '$'; // s = '¥......'
node(0); // node[0] : len = 0 偶根
node(-1); // node[1] : len = -1 奇根
fail[0] = 1;
}
int jump(int u) {
while (s[pos - len[u] - 1] != s[pos]) {
u = fail[u];
}
return u;
}
void extend(char c, int lb[]) {
s[++pos] = c;
int d = c - 'a';
int u = jump(las);
if (!ch[u][d]) {
int v = node(len[u] + 2);
fail[v] = ch[jump(fail[u])][d];
ch[u][d] = v;
}
las = ch[u][d];
cnt[las]++;
lb[pos] = max(lb[pos], len[las]);
// rb[pos - len[las] + 1] = max(rb[pos - len[las] + 1], len[las]);
// npos[las] = pos; // 记录最后出现的位置
}
} pam;
char s[PAMN];
int lb[PAMN];
int rb[PAMN];
int main() {
scanf("%s", s + 1);
int n = strlen(s + 1);
pam.init();
for (int i = 1; i <= n; i++) {
lb[i] = 0;
pam.extend(s[i], lb);
}
reverse(s + 1, s + 1 + n);
pam.init();
for (int i = 1; i <= n; i++) {
rb[i] = 0;
pam.extend(s[i], rb);
}
reverse(rb + 1, rb + 1 + n);
int ans = 0;
for (int i = 1; i < n; ++i) {
ans = max(ans, lb[i] + rb[i + 1]);
}
printf("%d\n", ans);
return 0;
}