2023.12.28做题纪要
Antisymmetry
水题???
二分+哈希:对于每两个字符中间的空隙二分左右的长度,判断条件是左边的异或后的字符串与右边的没异或的字符串相不相等。
不是水题。。。
manacher:方法很简单,就是 \(1\) 对应 \(0\),\(0\) 对应 \(1\) 直接硬跑。
至于为什么对:我们设在回文串中两个以对称轴对称的位置的两个位置 \(i, j\) 且 \(i < j\),则在大回文串里的以 \(i\),\(j\) 为对称轴的回文串长度至少相等。
此题也是同理,因为翻转 \(0,1\) 并不会改变回文的性质,所以 \(i,j\) 的回文串并不会影响,所以能直接跑。
对了,注意开 long long。
邻家
#include <bits/stdc++.h>
int N;
char string[510000];
class Manacher {
public:
char str[1010000], to[300];
int n, pail[1010000];
void Change(int len, char *temp) {
to['0'] = '1';
to['1'] = '0';
to['#'] = '#';
to['~'] = '~';
str[0] = '~';
str[++ n] = '#';
for (int i = 1; i <= len; ++ i) {
str[++ n] = temp[i];
str[++ n] = '#';
}
}
void manacher() {
for (int pos = 1, maxR = 0, mid = 0; pos <= n; ++ pos) {
if (pos <= maxR)
pail[pos] = std::min(pail[(mid << 1) - pos], maxR - pos + 1);
while (str[pos - pail[pos]] == to[str[pos + pail[pos]]])
pail[pos] ++;
if (pos + pail[pos] - 1 > maxR) {
maxR = pos + pail[pos] - 1;
mid = pos;
}
}
}
}manacher;
long long answer;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N >> (string + 1);
manacher.Change(N, string);
manacher.manacher();
for (int i = 1; i <= manacher.n; ++ i) {
if (manacher.str[i] == '#') {
answer += (manacher.pail[i] - 1) / 2ll;
}
}
std::cout << answer << '\n';
return 0;
}
模板:回文自动机
代码中的第 \(29\) 行能直接跳的原因如下:
举个例子先,若此时串为 \(bababa\),我们新加一个 \(b\) 字符在最后,则合法的最长回文串为 \(ababa\)。
此时我们开始找新加的 \(b\) 的失配指针,找到的是原最长回文串 \(ababa\) 的最后三个字母 \(aba\)。
因为我们合法的最长回文串的左边一位与我们新加的字符 \(b\) 相同,那么我们把最后三个字符在最长回文串中对称过来,取最长回文串中的前三个字符 \(aba\),因为我们已经处理过\(bababab\) 中的前 \(6\) 个字符,所以 \(babab\) 我们也处理过了。可以直接跳。
然后就没什么了。
小可爱
#include <bits/stdc++.h>
const int MAXN = 5e5 + 100;
char str[510000];
class Palindrome_Automaton {
public:
int cur, tot;
int child[MAXN][27], fail[MAXN], len[MAXN], sum[MAXN];
Palindrome_Automaton() {
tot = 1;
fail[0] = 1, fail[1] = 1;
len[0] = 0, len[1] = -1;
}
int GetFail(int x, int i) {
while (i - len[x] - 1 < 0 || str[i - len[x] - 1] != str[i])
x = fail[x];
return x;
}
void Insert(char c, int i) {
int x = GetFail(cur, i);
int ch = c - 'a', temp;
if (!child[x][ch]) {
len[++ tot] = len[x] + 2;
temp = GetFail(fail[x], i);
fail[tot] = child[temp][ch];
sum[tot] = sum[fail[tot]] + 1;
child[x][ch] = tot;
}
cur = child[x][ch];
}
}PAM;
int N, answer;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> (str + 1);
N = strlen(str + 1);
for (int i = 1; i <= N; ++ i) {
str[i] = (str[i] - 97 + answer) % 26 + 97;
PAM.Insert(str[i], i);
answer = PAM.sum[PAM.cur];
std::cout << answer << ' ';
}
std::cout << '\n';
return 0;
}
双倍回文
看题解,里面的人都说是板子题,我咋没感觉啊QAQ。
我觉得是个妙妙题,还是挺考验对 \(PAM\) 的理解的???
我们先定义一个 \(trans[i]\) 的数组表示在以点 \(i\) 为结尾的并且长度不超过 \(len[i]/2\) 的最长后缀。
然后像类似 \(fail\) 一样的处理就行了。
大姐姐
#include <bits/stdc++.h>
const int MAXN = 1e6 + 1000;
int N, answer;
char str[MAXN];
class Palindrome_Automaton {
public:
int cur, tot;
int len[MAXN], child[MAXN][27], fail[MAXN], trans[MAXN];
Palindrome_Automaton() {
tot = 1;
fail[0] = 1, fail[1] = 1;
len[0] = 0, len[1] = -1;
}
int GetFail(int x, int i) {
while (i - len[x] - 1 < 0 || str[i - len[x] - 1] != str[i])
x = fail[x];
return x;
}
void Insert(char c, int i) {
int x = GetFail(cur, i);
int ch = c - 'a', temp;
if (!child[x][ch]) {
len[++ tot] = len[x] + 2;
fail[tot] = child[GetFail(fail[x], i)][ch];
child[x][ch] = tot;
if (len[tot] <= 2) {
trans[tot] = fail[tot];
}
else {
temp = trans[x];
while (str[i - len[temp] - 1] != str[i] || (len[temp] + 2) * 2 > len[tot])
temp = fail[temp];
trans[tot] = child[temp][ch];
}
}
cur = child[x][ch];
}
void Answer() {
for (int i = 2; i <= tot; ++ i) {
if (len[trans[i]] * 2 == len[i] && len[trans[i]] % 2 == 0)
answer = std::max(answer, len[i]);
}
}
}PAM;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N >> (str + 1);
for (int i = 1; i <= N; ++ i) {
// std::cout << i << '\n';
PAM.Insert(str[i], i);
}
PAM.Answer();
std::cout << answer << '\n';
return 0;
}
模板:SAM
2023年12月28日
头好痛,感觉要长脑子了┭┮﹏┭┮