HDU 3948 The Number of Palindromes (后缀数组 + ST表)

文章目录

    • 题目大意
    • 思路
    • 代码

题目链接

题目大意

给你一个字符串,找出有多少个不同的回文子串

思路

把给的字符串反向拼接到原字符串后面,然后用一个字符分隔开两个字符串,这样问题就变成了求最长公共前缀。
用ST表预处理每两个后缀的最长公共前缀,分奇偶统计回文子串,之后去重即可
有关去重的操作,代码里有详细解释


一些碎碎念:在书上学到后缀数组这个知识点后,我把后缀数组那一小节后面的习题拿出来组了个题单,前两道题还好,稍微想一想就能想明白,这是第三题,我想了一下午,仍然没想出来该怎么单独用后缀数组来解决qwq
去网上查了查,发现有用manacher的,有用回文自动机的(触及到我知识的盲区了orz)
抱着“这书应该不能坑我”的想法,我觉得用我会的知识点应该足够解决,毕竟书上的提示里面没有涉及什么新的东西qwq
翻了翻以前收藏的模板,看到了后缀数组那里有一大坨的和ST表有关的代码,大彻大悟(bushi),又在去重那里较劲了半天,终于解决了QwQ

代码

#include

using namespace std;
#define mt(x, k) memset(x,k,sizeof(x))
const int mc = 200010;
int sa_t1[mc], sa_t2[mc], sa_cnt[mc];

bool cmp(const int *r, int a, int b, int l) {
    return r[a] == r[b] && r[a + l] == r[b + l];
}

void da(const int str[], int sa[], int rank[], int height[], int n, int m) {
    n++;
    int i, j, p, *x = sa_t1, *y = sa_t2;
    for (i = 0; i < m; i++)sa_cnt[i] = 0;
    for (i = 0; i < n; i++)sa_cnt[x[i] = str[i]]++;
    for (i = 1; i < m; i++)sa_cnt[i] += sa_cnt[i - 1];
    for (i = n - 1; i >= 0; i--)sa[--sa_cnt[x[i]]] = i;
    for (j = 1; j <= n; j <<= 1) {
        p = 0;
        for (i = n - j; i < n; i++)y[p++] = i;
        for (i = 0; i < n; i++)if (sa[i] >= j)y[p++] = sa[i] - j;
        for (i = 0; i < m; i++)sa_cnt[i] = 0;
        for (i = 0; i < n; i++)sa_cnt[x[y[i]]]++;
        for (i = 1; i < m; i++)sa_cnt[i] += sa_cnt[i - 1];
        for (i = n - 1; i >= 0; i--)sa[--sa_cnt[x[y[i]]]] = y[i];
        swap(x, y);
        p = 1;
        x[sa[0]] = 0;
        for (i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
        if (p >= n)break;
        m = p;
    }
    int k = 0;
    n--;
    for (i = 0; i <= n; i++)rank[sa[i]] = i;
    for (i = 0; i < n; i++) {
        if (k)k--;
        j = sa[rank[i] - 1];
        while (str[i + k] == str[j + k])k++;
        height[rank[i]] = k;
    }
}

int sa_rk[mc], sa_height[mc];
int RMQ[mc];
int mm[mc];
int best[20][mc];

void init_sa() {
    mt(sa_height, 0);
    mt(sa_cnt, 0);
    mt(sa_rk, 0);
    mt(sa_t1, 0);
    mt(sa_t2, 0);
}

void initRMQ(int n) {
    mm[0] = -1;
    for (int i = 1; i <= n; i++)
        mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
    for (int i = 1; i <= n; i++)best[0][i] = i;
    for (int i = 1; i <= mm[n]; i++)
        for (int j = 1; j + (1 << i) - 1 <= n; j++) {
            int a = best[i - 1][j];
            int b = best[i - 1][j + (1 << (i - 1))];
            if (RMQ[a] < RMQ[b])best[i][j] = a;
            else best[i][j] = b;
        }
}

int askRMQ(int a, int b) {
    int t;
    t = mm[b - a + 1];
    b -= (1 << t) - 1;
    a = best[t][a];
    b = best[t][b];
    return RMQ[a] < RMQ[b] ? a : b;
}

//求两个后缀的最长公共前缀
int lcp(int a, int b) {
    a = sa_rk[a];
    b = sa_rk[b];
    if (a > b)swap(a, b);
    return sa_height[askRMQ(a + 1, b)];
}

char str[mc];//输入字符串的
int r[mc];//处理字符串的
int sa[mc];

int main() {
    int t;
    cin >> t;
    for (int j = 0; j < t; ++j) {
        init_sa();
        scanf("%s", str);
        int len = strlen(str);
        int n = 2 * len + 1;
        for (int i = 0; i < len; i++)
            r[i] = str[i];
        //将字符串反向存储在r数组中
        for (int i = 0; i < len; i++)
            r[len + 1 + i] = str[len - 1 - i];
        //用一个(字符串中不存在的)符号,来分隔开两个字符串
        r[len] = '$';
        //为了计算后缀数组,结尾要为0
        r[n] = 0;
        da(r, sa, sa_rk, sa_height, n, 128);//一步到位,后缀数组和高度数组都计算完了
        for (int i = 1; i <= n; i++) RMQ[i] = sa_height[i];
        initRMQ(n);
        int ans = 0, cnt1 = 0, cnt2 = 0;
        int tmp;
        for (int i = 1; i <= n; i++) {//注意n是两个字符串合并后的总长
            //下面这两行用于记录已经统计过的回文子串
            cnt1 = min(cnt1, sa_height[i]);//记录统计过的以sa[i]为中心的奇数回文串
            cnt2 = min(cnt2, sa_height[i]);//记录统计过的以sa[i]为中心的偶数回文串
            if (sa[i] >= len) continue;//避免重复统计
            tmp = lcp(sa[i], n - sa[i] - 1);//统计以sa[i]为中心的奇数回文子串数量
            if (tmp > cnt1) ans += tmp - cnt1, cnt1 = tmp;
            //上面这行表示
            //如果以sa[i]为中心的奇数回文子串数量,大于统计过的以sa[i]为中心的奇数回文串数量
            //那说明我们统计到了一些新的回文子串
            //ans += tmp - cnt1当前统计的回文子串数,减去统计过的,就是新统计的回文子串数
            //同时更新cnt1,表示我们统计到了更多的回文子串
            //下面统计偶数回文子串同理
            tmp = lcp(sa[i], n - sa[i]);//统计以sa[i]为中心的偶数回文子串数量
            if (tmp > cnt2) ans += tmp - cnt2, cnt2 = tmp;
        }
        printf("Case #%d: %d\n", j + 1, ans);
    }
    return 0;
}

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