回文树的学习

 

理论部分参考论文:回文树及其实现 翁文涛

模板

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] ;  
    int num[MAXN] ;  
    int len[MAXN] ;//len[i]表示节点i表示的回文串的长度  
    int S[MAXN] ;//存放添加的字符  
    int last ;//指向上一个字符所在的节点,方便下一次add  
    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的子回文串!  
    }  
} ;

Palindromic Tree

【bzoj3676】[Apio2014]回文串

 

bzoj2565: 最长双回文串

https://www.lydsy.com/JudgeOnline/problem.php?id=2565

#include<bits/stdc++.h>
using namespace std;
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] ;
    int num[MAXN] ;
    int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
    int S[MAXN] ;//存放添加的字符
    int last ;//指向上一个字符所在的节点,方便下一次add
    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 ;
    }
    int 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] ++ ;
        return len[last];
    }

    void count () {
        for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
        //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
    }
} ;
Palindromic_Tree T;
char s[100050];
int len[100050];
int main(){
    scanf("%s",s);
    int n=strlen(s);
    T.init();
    int ans=0;
    for(int i=n-1;i>=0;i--){
        len[i]=T.add(s[i]);
    }
    T.init();
    for(int i=0;i<n-1;i++){
        ans=max(ans,T.add(s[i])+len[i+1]);
    }
    printf("%d\n",ans);
    return 0;
}

bzoj2160: 拉拉队排练

https://www.lydsy.com/JudgeOnline/problem.php?id=2160

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 1000005 ;
const int N = 26 ;
struct Palindromic_Tree {
    int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
    int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
    int cnt[MAXN] ;
    int num[MAXN] ;
    int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
    int S[MAXN] ;//存放添加的字符
    int last ;//指向上一个字符所在的节点,方便下一次add
    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 ;
    }
    int 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] ++ ;
        return len[last];
    }

    void count () {
        for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
        //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
    }
} ;
Palindromic_Tree T;
char s[MAXN];
ll len[MAXN];
ll n,k;
bool cmp(int x,int y){
    return x>y;
}
const ll mod=19930726;
ll fpow(ll a, ll b)
{
	ll ans = 1;//A
	a = a % mod;
	while (b > 0)
	{
		if (b&1)
			ans = ans * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return ans;
}
int main(){
    scanf("%lld%lld",&n,&k);
    scanf("%s",s);
    T.init();
    ll ans=1;
    ll m=0;
    for(int i=0;i<n;i++){
        T.add(s[i]);
    }
    T.count();
    int mx=0;
    for(int i=2;i<=n+2;i++){
        if(T.len[i]&1){
            len[T.len[i]]+=T.cnt[i];
            mx=max(mx,T.len[i]);
            m+=T.cnt[i];
        }
    }
    if(k>m){
        printf("-1\n");
        return 0;
    }
    for(int i=mx;i>=1;i-=2){
        ll mn=min(k,len[i]);
        k-=mn;
        if(mn) ans=(ans*fpow(i,mn))%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

Codeforces 17E. Palisection 

https://codeforces.com/problemset/problem/17/E

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2e6+5;
const int mod = 51123987;
int n,fa[N],len[N],dep[N],tot,last,p1[N],p2[N],ans;
int to[N],nxt[N],ww[N],head[N],cnt;
char s[N];
void init()
{
    fa[last=0]=fa[1]=1;
    len[tot=1]=-1;
    memset(head,0,sizeof(head));cnt=0;
}
void link(int u,int v,int c)
{
    to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=c;
    head[u]=cnt;
}
int tr(int v,int c)
{
    for (int e=head[v];e;e=nxt[e])
        if (ww[e]==c) return to[e];
    return 0;
}
void extend(int c,int n)
{
    int v=last;
    while (s[n-len[v]-1]!=s[n]) v=fa[v];
    if (!tr(v,c))
    {
        int u=++tot,k=fa[v];
        len[u]=len[v]+2;
        while (s[n-len[k]-1]!=s[n]) k=fa[k];
        fa[u]=tr(k,c);dep[u]=dep[fa[u]]+1;
        link(v,u,c);
    }
    last=tr(v,c);
}
int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    init();
    for (int i=1;i<=n;++i) extend(s[i]-'a',i),(ans+=(p1[i]=dep[last]))%=mod;
    ans=1ll*ans*(ans-1)/2%mod;
    reverse(s+1,s+n+1);
    init();
    for (int i=1;i<=n;++i) extend(s[i]-'a',i),p2[n-i+1]=dep[last];
    for (int i=n;i;--i) (p2[i]+=p2[i+1])%=mod;
    for (int i=1;i<=n;++i) ans=(ans-1ll*p1[i]*p2[i+1]%mod+mod)%mod;
    printf("%d\n",ans);
    return 0;
}

Palindrome Mouse

https://ac.nowcoder.com/acm/contest/886/C

#include<bits/stdc++.h>
using namespace std;
#define ll long long
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] ;
    int num[MAXN] ;//右端点在这一位子串的个数
    int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
    int S[MAXN] ;//存放添加的字符
    int last ;//指向上一个字符所在的节点,方便下一次add
    int n ;//字符数组指针
    int p ;//节点指针
    int sz[MAXN],c[MAXN],vis[MAXN];
    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 ;
    }
    int 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] ++ ;
        return len[last];
    }

    void count () {
        for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
        //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
    }
    int dfs(int u){
        sz[u]=1;
        c[u]=0;
        for(int t=u;!vis[t]&&t>1;t=fail[t]){
            vis[t]=u;
            c[u]++;
        }
        for(int i=0;i<26;i++){
            if(next[u][i]==0) continue;
            sz[u]+=dfs(next[u][i]);
        }
        for(int t=u;vis[t]==u&&t>1;t=fail[t]){
            vis[t]=0;
        }
        return sz[u];
    }
    long long solve(){
        dfs(0);dfs(1);
        long long ans=0;
        for(int i=2;i<p;i++){
            ans+=1ll*sz[i]*c[i];
        }
        ans=ans-p+2;
        return ans;
    }
} ;
Palindromic_Tree T;
char s[MAXN];
int n;
int main(){
    int ca=1;
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",s);
        n=strlen(s);
        T.init();
        ll ans=0;
        for(int i=0;i<n;i++){
            T.add(s[i]);
        }
        printf("Case #%d: %lld\n",ca++,T.solve());
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值