【学习小结】回文串相关知识

回文自动机(回文树)

回文树的性质和构建

yyb

功能
求串S前缀0~i内本质不同回文串的个数
求串S内每一个本质不同回文串出现的次数
求串S内回文串的个数(其实就是1和2结合起来)
求以下标i结尾的回文串的个数

和AC自动机相似
回文树每个节点都代表一个回文子串,其fail指向最长回文后缀。并且该后缀也是一个border
要知道一个节点(一个位置)结尾的所有回文串,只需要跳完fail
但是统计一个点开始的,则需要把串倒过来建回文树
一个节点代表的回文串出现次数:fail树size之和。
从根到一个节点的路径,是该节点代表回文串的一半

模板

/*
最小回文划分
此代码求[1..i]的最小偶数个回文和奇数个回文串的划分。注意拆分成k个则一定可以拆分成k + 2个(两头独立)
利用slink链长logn的性质,可以用来求诸如回文划分方案数等东西

 */
namespace pam{
   
	const int minchar = 'a';
	const int maxchar = 26;
	const int N = 3e5 + 10;

	int tot,last,len[N],fail[N],nxt[N][maxchar],sz[N],quick[N][maxchar],pnt[N],half[N];
	int f[N],trim[N],pos[N],diff[N],slink[N];
	int ans[N][2],s_ans[N][2];

	void init(){
   
		rep(i,0,tot) len[i] = fail[i] = sz[i] = half[i] = pnt[i] = f[i] = trim[i] = 0 , memset(nxt[i],0,sizeof nxt[i]);
		ch[0]='*',tot=1,len[1]=-1,fail[0]=1;
		rep(i,0,25) quick[0][i] = 1;
		last = 0;
	}

	void ins(int c,int id){
   
		if ( ch[id] != ch[id - len[last] - 1] ) last = quick[last][c];
		//while(ch[id]!=ch[id-len[last]-1]) last=fail[last];
		if(nxt[last][c]) last=nxt[last][c];
		else{
   
			//int k=fail[last];
			//while(ch[id]!=ch[id-len[k]-1]) k=fail[k];
			//k=nxt[k][c];
			int k = nxt[quick[last][c]][c];
			int v = ++tot;
			fail[v]=k,len[v]=len[last]+2;
			memcpy(quick[v],quick[k],sizeof quick[k]);//字符集很大,可持久化quick数组
			quick[v][ch[id - len[k]] - minchar] = k;

			diff[v] = len[v] - len[fail[v]];
			if ( diff[v] == diff[fail[v]] ) slink[v] = slink[fail[v]];
			else slink[v] = fail[v];
			/*
			求长度小于等于len[v] / 2的最长回文后缀,复杂度均摊正确
			 if ( len[v] == 1 ) half[v] = 0;
			else{
				int p = half[last];
				while ( ch[id] != ch[id - len[p] - 1] || (len[p] + 2 > len[v] / 2) ) p = fail[p];
				half[v] = nxt[p][c];
			}*/

			nxt[last][c]=v;
			pnt[v] = last;
			last = v;
		}
		sz[last]++;
		pos[id] = last;
	}

	void build(){
   
		init();
		rep(i,1,n){
   
			ins(ch[i] - minchar,i);
		}
	//	rep(i,1,n) cout<<i<<" : "<<pos[i]<<" "<<len[pos[i]]<<endl;
	}
	void solve(){
   
		ans[0][0] = 0;
		ans[0][1] = inf;
		rep(i,1,n) ans[i][0] = ans[i][1] = inf;
		rep(i,1,n){
   
			//cout<<"cur : "<<i<<endl;
			for (int x = pos[i] ; len[x] > 0 ; x = slink[x]){
   
			//	cout<<x<<" "<<slink[x]<<" "<<len[slink[x]]<<" "<<len[x]<<" "<<diff[x]<<endl;
				s_ans[x][0] = ans[i - len[slink[x]] - diff[x]][0];
				s_ans[x][1] = ans[i - len[slink[x]] - diff[x]][1];
				if ( diff[x] == diff[fail[x]] ){
   
					s_ans[x][0] = min(s_ans[fail[x]][0],s_ans[x][0]);
					s_ans[x][1] = min(s_ans[fail[x]][1],s_ans[x][1]);
				}
				ans[i][0] = min(ans[i][0],s_ans[x][1] + 1);
				ans[i][1] = min(ans[i][1],s_ans[x][0] + 1);
			}
			if ( ans[i][1] > n ) printf("-1 ");
			else printf("%d ",ans[i][1]);
			if ( ans[i][0] > n ) printf("-2\n");
			else printf("%d\n",ans[i][0]);
		}	
	}
}
using namespace pam
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值