回文树练习题

贴(改)模板大集合系列
大概包括了pam的这些问题:
1.sz的意义:本质不同的回文子串个数
2.cnt的意义:当前回文子串出现的次数
3.num的意义:靠最右边的回文子串个数、新加一个字符产生的回文子串个数、暴力fail的层数
4.half的意义:长度小于等于当前回文子串的一半的回文后缀的节点
5.支持两边加字符的回文自动机的做法:维护一左一右两个last

Tsinsen A1280
问题描述
  顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
  输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100007;
char s[maxn];
struct PAM{
    int next[maxn][26];
    int fail[maxn];
    int cnt[maxn];
    int len[maxn];
    int S[maxn];
    int last,n,sz;
    int newnode(int l){
        for(int i=0;i<26;i++){
            next[sz][i]=0;
        }
        cnt[sz]=0;
        len[sz]=l;
        return sz++;
    }
    void init(){
        sz=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getfail(int x){
        while(S[n-len[x]-1]!=S[n])x=fail[x];
        return x;
    }
    void insert(char ch){
        int c=ch-'a';
        S[++n]=c;
        int u=getfail(last);
        if(!next[u][c]){
            int v=newnode(len[u]+2);
            fail[v]=next[getfail(fail[u])][c];
            next[u][c]=v;
        }
        last=next[u][c];
        cnt[last]++;
    }
    void count(){
        for(int i=sz-1;i;i--){
            cnt[fail[i]]+=cnt[i];
        }
    }
}pam;
int pre[maxn];
int main(){
    int ans=0;
    scanf("%s",s);
    int n=strlen(s);
    pam.init();
    for(int i=0;i<n;i++){
        pam.insert(s[i]);
        pre[i]=pam.len[pam.last];
    }
    pam.init();
    for(int i=n-1;i>0;i--){
        pam.insert(s[i]);
        ans=max(ans,pre[i-1]+pam.len[pam.last]);
    }
    printf("%lld\n",ans);
}

Tsinsen A1393
问题描述
  给你一个长度 n (1 ≤ n ≤ 2·106) 的只由小写字母组成的字符串s。
  我们考虑s的所有连续且回文的子串集合P。位置不同但内容相同的两个串算作不同。
  问从P中选出两个串且他们在s中有公共位置的方法数有几个?

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=51123987;
const int inv2=(mod+1)/2;
const int maxn=2000007;
char s[maxn];
struct PAM{
    int next[maxn][26];
    int fail[maxn];
    int cnt[maxn];
    int num[maxn];
    int len[maxn];
    int S[maxn];
    int last,n,sz;
    int newnode(int l){
        for(int i=0;i<26;i++){
            next[sz][i]=0;
        }
        cnt[sz]=0;
        len[sz]=l;
        return sz++;
    }
    void init(){
        sz=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getfail(int x){
        while(S[n-len[x]-1]!=S[n])x=fail[x];
        return x;
    }
    void insert(char ch){
        int c=ch-'a';
        S[++n]=c;
        int u=getfail(last);
        if(!next[u][c]){
            int v=newnode(len[u]+2);
            fail[v]=next[getfail(fail[u])][c];
            next[u][c]=v;
            num[v]=num[fail[v]]+1;
        }
        last=next[u][c];
        cnt[last]++;
    }
    ll count(){
        ll ret=0;
        for(int i=sz-1;i;i--){
            cnt[fail[i]]+=cnt[i];
            ret=(ret+cnt[i])%mod;
        }
        return ret;
    }
}pam;
ll suf[maxn];
int main(){
    int ans=0;
    int n;
    scanf("%d",&n);
    scanf("%s",s);
    pam.init();
    for(int i=n-1;i>=0;i--){
        pam.insert(s[i]);
        suf[i]=(suf[i+1]+pam.num[pam.last])%mod;
    }
    ll tot=suf[0]*(suf[0]-1+mod)%mod*inv2%mod;
    pam.init();
    ll rev=0;
    for(int i=0;i<n;i++){
        pam.insert(s[i]);
        rev=(rev+pam.num[pam.last]*suf[i+1])%mod;
    }
    printf("%lld\n",(tot-rev+mod)%mod);
}

ACM-ICPC 2018 南京赛区网络预赛 I.Skr
问题描述
求本质不同回文子串的hash和

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=2000007;
char s[maxn];
ll pw[maxn];
ll ans=0;
struct PAM{
	int next[maxn][10];
	int fail[maxn];
	int cnt[maxn];
	int len[maxn];
	ll val[maxn];
	int S[maxn];
	int last,n,sz;
	int newnode(int l){
		for(int i=0;i<10;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		val[sz]=0;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last=0;
		n=0;
		S[n]=-1;
		fail[0]=1;
	}
	int getfail(int x){
		while(S[n-len[x]-1]!=S[n])x=fail[x];
		return x;
	}
	void insert(char ch){
		int c=ch-'0';
		S[++n]=c;
		int u=getfail(last);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			if(len[u]==-1){
				val[v]=c;
			}
			else{
				val[v]=(val[u]*10+c+c*pw[len[u]+1])%mod;
			}
			ans=(ans+val[v])%mod;
			fail[v]=next[getfail(fail[u])][c];
			next[u][c]=v;
		}
		last=next[u][c];
		cnt[last]++;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam;
int main(){
	scanf("%s",s);
	pam.init();
	pw[0]=1;
	for(int i=1;i<maxn;i++)pw[i]=pw[i-1]*10%mod;
	for(int i=0;s[i];i++){
		pam.insert(s[i]);
	}
	printf("%lld\n",ans);
}

Bzoj 2342
问题描述
求最长双倍回文偶回文子串长度

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=500007;
char s[maxn];
struct edge{
	int to,next;
}e[maxn];
int head[maxn];
int cnt;
void init(){
	cnt=0;
	memset(head,-1,sizeof(head));
}
void addedge(int u,int v){
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt++;
}
struct PAM{
	int next[maxn][26];
	int fail[maxn];
	int cnt[maxn];
	int num[maxn];
	int len[maxn];
	int S[maxn];
	int last,n,sz;
	int newnode(int l){
		for(int i=0;i<26;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last=0;
		n=0;
		S[n]=-1;
		fail[0]=1;
		addedge(1,0);
	}
	int getfail(int x){
		while(S[n-len[x]-1]!=S[n])x=fail[x];
		return x;
	}
	void insert(char ch){
		int c=ch-'a';
		S[++n]=c;
		int u=getfail(last);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail(fail[u])][c];
			addedge(fail[v],v);
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
		}
		last=next[u][c];
		cnt[last]++;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam;
int vis[maxn];
int ans;
void dfs(int u){
	if(pam.len[u]%4==0&&vis[pam.len[u]/2]){
		ans=max(ans,pam.len[u]);
	}
	for(int i=head[u];~i;i=e[i].next){
		int v=e[i].to;
		vis[pam.len[v]]++;
		dfs(v);
		vis[pam.len[v]]--;
	}
}
int main(){
	int n;
	scanf("%d",&n);
	scanf("%s",s);
	init();
	pam.init();
	for(int i=0;i<n;i++){
		pam.insert(s[i]);
	}
	ans=0;
	dfs(1);
	printf("%d\n",ans);
}

Hdu 5421
问题描述
维护一个字符串,支持4种操作
1. 左增加一个字符
2. 右增加一个字符
3. 查询本质不同的回文子串个数
4. 查询回文子串个数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=200007;
typedef long long ll;
struct PAM{
	int next[maxn][26];
	int fail[maxn];
	int cnt[maxn];
	int num[maxn];
	int len[maxn];
	int S[maxn];
	int last1,last2,l,r,sz;
	int newnode(int l){
		for(int i=0;i<26;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last1=last2=0;
		l=100002;
		r=l-1;
		memset(S,-1,sizeof(S));
		fail[0]=1;
	}
	int getfail1(int x){
		while(S[l+len[x]+1]!=S[l])x=fail[x];
		return x;
	}
	int getfail2(int x){
		while(S[r-len[x]-1]!=S[r])x=fail[x];
		return x;
	}
	void insertbefore(char ch){
		int c=ch-'a';
		S[--l]=c;
		int u=getfail1(last1);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail1(fail[u])][c];
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
		}
		last1=next[u][c];
		cnt[last1]++;
		if(len[last1]==r-l+1)last2=last1;
	}
	void insertafter(char ch){
		int c=ch-'a';
		S[++r]=c;
		int u=getfail2(last2);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail2(fail[u])][c];
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
		}
		last2=next[u][c];
		cnt[last2]++;
		if(len[last2]==r-l+1)last1=last2;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam;
int main(){
	int n;
	while(~scanf("%d",&n)){
		pam.init();
		ll sum=0;
		for(int i=1;i<=n;i++){
			int op;
			char str[3];
			scanf("%d",&op);
			if(op==1){
				scanf("%s",str);
				pam.insertbefore(str[0]);
				sum+=pam.num[pam.last1];
			}
			else if(op==2){
				scanf("%s",str);
				pam.insertafter(str[0]);
				sum+=pam.num[pam.last2];
			}
			else if(op==3){
				printf("%d\n",pam.sz-2);
			}
			else {
				printf("%lld\n",sum);
			}
		}
	}
}

CF 245H
题目描述
给一个长为5000的字符串,多次查询区间回文子串个数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5007;
char s[maxn];
struct PAM{
	int next[maxn][26];
	int fail[maxn];
	int cnt[maxn];
	int num[maxn];
	int len[maxn];
	int S[maxn];
	int last,n,sz;
	int newnode(int l){
		for(int i=0;i<26;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last=0;
		n=0;
		S[n]=-1;
		fail[0]=1;
	}
	int getfail(int x){
		while(S[n-len[x]-1]!=S[n])x=fail[x];
		return x;
	}
	void insert(char ch){
		int c=ch-'a';
		S[++n]=c;
		int u=getfail(last);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail(fail[u])][c];
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
		}
		last=next[u][c];
		cnt[last]++;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam;
int n;
int ans[5003][5003];
int main(){
	scanf("%s",s);
	n=strlen(s);
	for(int l=0;l<n;l++){
		pam.init();
		int sum=0;
		for(int r=l;r<n;r++){
			pam.insert(s[r]);
			sum+=pam.num[pam.last];
			ans[l][r]=sum;
		}
	}
	int q;
	scanf("%d",&q);
	for(int i=0;i<q;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",ans[l-1][r-1]);
	}
}

CodeChef Palindromeness
题目描述
每个字符串的价值定义如下:

  1. 非回文串价值为0
  2. 若回文串S长度为len,则V(S)=V(S[0…len/2-1])+1
    求给定字符串所有子串的价值和
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100007;
char s[maxn];
struct PAM{
	int next[maxn][26];
	int fail[maxn];
	int cnt[maxn];
	int num[maxn];
	int len[maxn];
	int dp[maxn];
	int sum[maxn];
	int S[maxn];
	int half[maxn];
	int last,n,sz;
	int newnode(int l){
		for(int i=0;i<26;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		dp[sz]=0;
		sum[sz]=0;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last=0;
		n=0;
		S[n]=-1;
		fail[0]=1;
	}
	int getfail(int x){
		while(S[n-len[x]-1]!=S[n])x=fail[x];
		return x;
	}
	int gethalf(int x,int l){
		while(S[n-len[x]-1]!=S[n]||len[x]+2>l)x=fail[x];
		return x;
	}
	void insert(char ch){
		int c=ch-'a';
		S[++n]=c;
		int u=getfail(last);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail(fail[u])][c];
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
			if(len[v]==1)half[v]=1;
			else half[v]=next[gethalf(half[u],len[v]/2)][c];
			if(len[half[v]]==len[v]/2)dp[v]=dp[half[v]]+1;
			else dp[v]=1;
			sum[v]=dp[v]+sum[fail[v]];
		}
		last=next[u][c];
		cnt[last]++;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam;
ll ans;
int n;
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%s",s);
		n=strlen(s);
		pam.init();
		ans=0;
		for(int i=0;i<n;i++){
			pam.insert(s[i]);
			ans+=pam.sum[pam.last];
		}
		printf("%lld\n",ans);
	}
}

Aizu 2292
题目描述
求两个字符串的公共回文子串对数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100007;
char s1[maxn],s2[maxn];
struct PAM{
	int next[maxn][26];
	int fail[maxn];
	int cnt[maxn];
	int num[maxn];
	int len[maxn];
	int S[maxn];
	int last,n,sz;
	int newnode(int l){
		for(int i=0;i<26;i++){
			next[sz][i]=0;
		}
		cnt[sz]=0;
		len[sz]=l;
		return sz++;
	}
	void init(){
		sz=0;
		newnode(0);
		newnode(-1);
		last=0;
		n=0;
		S[n]=-1;
		fail[0]=1;
	}
	int getfail(int x){
		while(S[n-len[x]-1]!=S[n])x=fail[x];
		return x;
	}
	void insert(char ch){
		int c=ch-'A';
		S[++n]=c;
		int u=getfail(last);
		if(!next[u][c]){
			int v=newnode(len[u]+2);
			fail[v]=next[getfail(fail[u])][c];
			next[u][c]=v;
			num[v]=num[fail[v]]+1;
		}
		last=next[u][c];
		cnt[last]++;
	}
	void count(){
		for(int i=sz-1;i;i--){
			cnt[fail[i]]+=cnt[i];
		}
	}
}pam1,pam2;
ll ans;
int n,m;
void dfs(int u1,int u2){
	if(u1>1)ans+=1ll*pam1.cnt[u1]*pam2.cnt[u2];
	for(int i=0;i<26;i++){
		int v1=pam1.next[u1][i];
		int v2=pam2.next[u2][i];
		if(v1&&v2)dfs(v1,v2);
	}
}
int main(){
	scanf("%s%s",s1,s2);
	n=strlen(s1);
	m=strlen(s2);
	pam1.init();
	pam2.init();
	for(int i=0;i<n;i++){
		pam1.insert(s1[i]);
	}
	pam1.count();
	for(int i=0;i<m;i++){
		pam2.insert(s2[i]);
	}
	pam2.count();
	ans=0;
	dfs(0,0);
	dfs(1,1);
	printf("%lld\n",ans);
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值