Finding Palindromes -- 字典树 & 扩展kmp

题目链接

题目大意:给你 n 个字符串,两两串联得到 n×n 个新字符串,问其中有几个是回文串,保证 n 的字符串的总长度不超过 2e6。

简要分析:
两个字符串 s 和 t ,长度分别是 i 和 j,假设它们可以组成回文串,若 i < j,则 s 是 t 反转的前缀,若 i>= j,则 t 反转是 s 的前缀。
也就是说,先把 t 反转,然后与 s 匹配,若 s 先匹配完,则看 t 的剩余部分是否是回文,若 t 先匹配完,则看 s 的剩余部分是否是回文。

总结一下:需要判断一个字符串的前缀回文和后缀回文,扩展kmp就可以解决。而匹配过程可以用字典树进行优化,把 n 个字符串倒序插入字典树,然后进行 n 次匹配即可。

字典树的标记,属性1:匹配串在该点结束后有多少个字符串剩余部分是回文,即 前缀回文的个数。 属性2:有多少个字符串在该节点结束。

#include
#include
#include

using namespace std;

typedef long long LL;
const int maxn = 2e6+7;

int trie[maxn][26], tot;  // 字典树 & 前缀树
int exist[maxn]; // 标记终点个数
LL num[maxn];    // 统计前缀个数(可能很大)
char s[maxn], t[maxn<<1];  // s 为文本串 t 为加 # 串
int Next[maxn<<1]; // t 中以 i 为中心的最长回文半径
bool pre[maxn];  // 判断前缀回文串
bool suf[maxn];  // 判断后缀回文串
int start[maxn]; // 每个字符串开始的位置

void insert(char s[], int x) { // 插入字典树
    int pos = 0;
    for(int i = start[x+1] - 1; i >= start[x]; -- i) { // 倒序插入
        int j = s[i] - 'a';
        if (trie[pos][j] == 0) {
            trie[pos][j] = ++tot;
        }
        pos = trie[pos][j];
        if (i != start[x] && pre[i-1]) num[pos] ++;   // 从该点之后的回文个数
    }
    exist[pos] ++; // 标记终点
}

LL search(char s[], int x) { // 查询相同前缀个数
    LL pos = 0, res = 0, i;
    for(i = start[x]; i <= start[x+1]-1; ++ i) {
        int j = s[i] - 'a';
        pos = trie[pos][j];
        if (pos == 0) break;
        if(i==start[x+1]-1 || suf[i+1]) res += exist[pos]; // 前缀较短
    }
    if (i == start[x+1]) res += num[pos]; // 前缀较长
    return res;
}

void manacher(char s[], int x) {
    for(int i = start[x]; i <= start[x+1]; ++ i) {
        t[(i<<1)-1] = '#';
        t[(i<<1)] = s[i];
    }
    int po, l = (start[x]-1<<1), r = (start[x+1]<<1);
    Next[po=l+1] = 1;
    for(int i = l+2; i < r-1; ++ i) { // 第一个和最后一个 # 不需要判断
        if (Next[(po<<1)-i] + i < Next[po] + po) Next[i] = Next[(po<<1)-i];
        else { // 已知区间全部匹配
            int j = (po + Next[po] <= i) ? 1 : Next[po] + po - i;
            while(i-j>l && i+j<r && t[i+j] == t[i-j]) ++j;
            Next[(po=i)] = j;
        }
        if (i - Next[i] == l) pre[start[x]+Next[i]-2] = true; // 前缀回文串权值
        if (i + Next[i] == r) suf[start[x+1]-Next[i]+1] = true; // 后缀回文串权值
    }
}


int main() {
    int n; scanf("%d", &n);
    start[1] = 1;
    tot = 0;
    for(int i = 1; i <= n; ++ i) {
        int len; scanf("%d%s", &len, s+start[i]);
        start[i+1] = start[i]+len;
        manacher(s, i);   // 判断每个字符串的前缀回文和后置回文
        insert(s, i);     // 倒序插入前缀树
    }
    LL res = 0;
    for(int i = 1; i <= n; ++ i) {
        res += search(s, i); // 查询前缀树
    }
    printf("%lld\n", res);
    return 0;
}

你可能感兴趣的:(字符串)