zoukankan      html  css  js  c++  java
  • 回文树(模板)+ 例题

    引用:
    Palindromic Tree——回文树【处理一类回文串问题的强力工具】
    回文树练习题集

    首先,回文树有何功能?
    假设我们有一个串S,S下标从0开始,则回文树能做到如下几点:

    1.求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
    2.求串S内每一个本质不同回文串出现的次数
    3.求串S内回文串的个数(其实就是1和2结合起来)
    4.求以下标i结尾的回文串的个数

    模板:

    const int MAXN = 100005 ;  
    const int N = 26 ;  
    
    struct Palindromic_Tree {  
        int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成  
        int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点  
        int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的) 
        int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
        int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
        int S[MAXN] ;//存放添加的字符  
        int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
        int n ;//表示添加的字符个数。
        int p ;//表示添加的节点个数。
    
        int newnode ( int l ) {//新建节点  
            for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;  
            cnt[p] = 0 ;  
            num[p] = 0 ;  
            len[p] = l ;  
            return p ++ ;  
        }  
    
        void init () {//初始化  
            p = 0 ;  
            newnode (  0 ) ;  
            newnode ( -1 ) ;  
            last = 0 ;  
            n = 0 ;  
            S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判  
            fail[0] = 1 ;  
        }  
    
        int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的  
            while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;  
            return x ;  
        }  
    
        void add ( int c ) {  
            c -= 'a' ;  
            S[++ n] = c ;  
            int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置  
            if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串  
                int now = newnode ( len[cur] + 2 ) ;//新建节点  
                fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转  
                next[cur][c] = now ;  
                num[now] = num[fail[now]] + 1 ;  
            }  
            last = next[cur][c] ;  
            cnt[last] ++ ;  
        }  
    
        void count () {  
            for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;  
            //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!  
        }  
    } ;  

    例题1:
    BZOJ3676
    求一个字符串中所有回文字串的 出现次数与长度乘积 的最大值。

    #include<iostream>  
    #include<cstdio>  
    #include<cstring>  
    #include<algorithm>  
    using namespace std;  
    
    const int maxn = 300010*2;  
    const int ALP = 26;  
    
    struct PAM{  
        int next[maxn][ALP];  
        int fail[maxn];  
        int cnt[maxn];  
        int num[maxn];  
        int len[maxn];  
        int s[maxn];  
        int last,n,p;  
    
        int newnode(int l){  
            for(int i=0;i<ALP;i++)  
                next[p][i]=0;  
            cnt[p]=num[p]=0;  
            len[p]=l;  
            return p++;  
        }  
        void init(){  
            p = 0;  
            newnode(0);  
            newnode(-1);  
            last = 0;  
            n = 0;  
            s[n] = -1;  
            fail[0] = 1;  
        }  
        int get_fail(int x){  
            while(s[n-len[x]-1] != s[n]) x = fail[x];  
            return x;  
        }  
        void add(int c){  
            c = c-'a';  
            s[++n] = c;  
            int cur = get_fail(last);  
            if(!next[cur][c]){  
                int now = newnode(len[cur]+2);  
                fail[now] = next[get_fail(fail[cur])][c];  
                next[cur][c] = now;  
                num[now] = num[fail[now]] + 1;  
            }  
            last = next[cur][c];  
            cnt[last]++;  
        }  
        void count(){  
            for(int i=p-1;i>=0;i--)  
                cnt[fail[i]] += cnt[i];  
        }  
    }pam;  
    
    char s[maxn];  
    int main(){  
        scanf("%s",s);  
        int len = strlen(s);  
        pam.init();  
        for(int i=0;i<len;i++){  
            pam.add(s[i]);  
        }  
        pam.count();  
        long long ret = 0;  
        for(int i=2;i<pam.p;i++){  
            ret = max((long long)pam.len[i]*pam.cnt[i],ret);  
        }  
        cout<<ret<<endl;  
        return 0;  
    }  

    例题2:
    UVA7041
    给出两个仅包含小写字符的字符串 A 和 B ;
    求:对于 A 中的每个回文子串,B 中和该子串相同的子串个数的总和。

    从0和1两个根节点DFS下去,如果两个相同的节点同时存在就统计答案。

    #include<iostream>  
    #include<cstdio>  
    #include<cstring>  
    #include<algorithm>  
    using namespace std;  
    const int maxn = 200010*2;  
    const int ALP = 26;  
    typedef long long ll;  
    
    struct PAM{  
        int next[maxn][ALP];  
        int fail[maxn];  
        int len[maxn];  
        int num[maxn];  
        int cnt[maxn];  
        int s[maxn];  
        int last,n,p;  
    
        int newnode(int le){  
            for(int i=0;i<ALP;i++)  
                next[p][i] = 0;  
            cnt[p] = 0;  
            num[p] = 0;  
            len[p] = le;  
            return p++;  
        }  
        void init(){  
            p = 0;  
            newnode(0);  
            newnode(-1);  
            last = 0;  
            n = 0;  
            s[n] = -1;  
            fail[0] = 1;  
        }  
        int get_fail(int x){  
            while(s[n-len[x]-1] != s[n]) x = fail[x];  
            return x;  
        }  
        void add(int c){  
            c -= 'a';  
            s[++n] = c;  
            int cur = get_fail(last);  
            if(!next[cur][c]){  
                int now = newnode(len[cur]+2);  
                fail[now] = next[get_fail(fail[cur])][c];  
                next[cur][c] = now;  
                num[now] = num[fail[now]] + 1;  
            }  
            last = next[cur][c];  
            cnt[last]++;  
        }  
        void count(){  
            for(int i=p-1;i>=0;i--)  
                cnt[fail[i]] += cnt[i];  
        }  
    }pam1,pam2;  
    ll dfs(int an,int bn){  
        ll ret = 0;  
        for(int i=0;i<ALP;i++) if(pam1.next[an][i]!=0 && pam2.next[bn][i]!=0)  
            ret += (ll)pam1.cnt[pam1.next[an][i]] * pam2.cnt[pam2.next[bn][i]]  
                + dfs(pam1.next[an][i],pam2.next[bn][i]);  
        return ret;  
    }  
    
    char s1[maxn],s2[maxn];  
    int main(){  
        int t,cas=0;cin>>t;  
        while(t--){  
            scanf("%s%s",s1,s2);  
            pam1.init();  
            pam2.init();  
            int len1 = strlen(s1) , len2 = strlen(s2);  
            for(int i=0;i<len1;i++) pam1.add(s1[i]);  
            for(int i=0;i<len2;i++){  
                pam2.add(s2[i]);  
            }  
            pam1.count();  
            pam2.count();  
            ll ret = dfs(0,0) + dfs(1,1);  
            printf("Case #%d: ",++cas);  
            printf("%lld
    ",ret);  
        }  
        return 0;  
    }  

    例题3:R - 也许这是唯一能阻止乐爷AK的方法( Just for Fun )

    题意:给你一个母串,你的初始串是一个空串s,根据母串中的字母来对s进行操作,每次只有两种,末尾添加字符或者末尾删除字符,求每次操作完以后s中回文串的个数。

    裸的回文树hhh

    #include<cstdio>
    #include<string.h>
    #include<cstring>
    #include<string>
    using namespace std;
    
    const int MAXN = 1e4 + 5;
    const int N = 26;
    
    const int maxn = 1e4 + 5;
    const int ALP = 26;
    
    struct PAM {
        int next[maxn][ALP];
        int fail[maxn];
        int cnt[maxn];
        int num[maxn];
        int len[maxn];
        int s[maxn];
        int last, n, p;
    
        int newnode(int l) {
            for (int i = 0; i<ALP; i++)
                next[p][i] = 0;
            cnt[p] = num[p] = 0;
            len[p] = l;
            return p++;
        }
        void init() {
            p = 0;
            newnode(0);
            newnode(-1);
            last = 0;
            n = 0;
            s[n] = -1;
            fail[0] = 1;
        }
        int get_fail(int x) {
            while (s[n - len[x] - 1] != s[n]) x = fail[x];
            return x;
        }
        void add(int c) {
            c = c - 'a';
            s[++n] = c;
            int cur = get_fail(last);
            if (!next[cur][c]) {
                int now = newnode(len[cur] + 2);
                fail[now] = next[get_fail(fail[cur])][c];
                next[cur][c] = now;
                num[now] = num[fail[now]] + 1;
            }
            last = next[cur][c];
            cnt[last]++;
        }
        void count() {
            for (int i = p - 1; i >= 0; i--)
                cnt[fail[i]] += cnt[i];
        }
    }tree;
    
    
    char s[MAXN];
    string snew;
    int q, ans[MAXN];
    
    int main() {
        scanf("%d", &q);
        scanf("%s", s);
        for (int i = 0; i < q; i++) {
            if (s[i] == '-') snew.pop_back();
            else snew.push_back(s[i]);
            tree.init();
            for (int j = 0; j < snew.size(); j++) {
                tree.add(snew[j]);
            }
            tree.count();
            for (int j = 2; j < tree.p; j++) {
                ans[i] += tree.cnt[j];
            }
        }
        for (int i = 0; i < q; i++) {
            printf("%d ", ans[i]);
        }
        return 0;
    }
  • 相关阅读:
    UVA 408 (13.07.28)
    linux概念之用户,组及权限
    Java实现 蓝桥杯 历届试题 网络寻路
    Java实现 蓝桥杯 历届试题 约数倍数选卡片
    Java实现 蓝桥杯 历届试题 约数倍数选卡片
    Java实现 蓝桥杯 历届试题 约数倍数选卡片
    Java实现 蓝桥杯 历届试题 约数倍数选卡片
    Java实现 蓝桥杯 历届试题 约数倍数选卡片
    Java实现 蓝桥杯 历届试题 九宫重排
    Java实现 蓝桥杯 历届试题 九宫重排
  • 原文地址:https://www.cnblogs.com/romaLzhih/p/9489823.html
Copyright © 2011-2022 走看看