回文自动机模板

回文自动机每个节点代表了一个回文串

能求出来的有:

1 本质不同的回文串的个数(tot - 1)

2 每种回文串出现的次数(cnt数组)

3 每种回文串的长度 (len数组)

4 以当前节点为后缀的回文串个数 (sed数组)

5 每个回文串在原串出现的位置 (record数组)

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
typedef unsigned long long ull;
typedef long long ll;
char s[N];
int record[N];
struct Palindrome_tree{
	int nxt[N][26];
	int fail[N]; // 当前节点最长回文后缀的节点
	int len[N]; // 当前节点表示的回文串的长度
	int cnt[N]; // 当前节点回文串的个数, 在getcnt后可得到全部
	int sed[N]; // 以当前节点为后缀的回文串的个数
	int tot; // 节点个数
	int last; // 上一个节点
	void init()
	{
		tot = 0;
		memset(fail, 0, sizeof fail);
		memset(cnt, 0, sizeof cnt);
		memset(sed, 0, sizeof sed);
		memset(len, 0, sizeof len);
		memset(nxt, 0, sizeof nxt);
	}
	void build()
	{
		len[0] = 0, len[1] = -1; // 0为偶数长度根, 1为奇数长度根
		tot = 1, last = 0;
		fail[0] = 1;
	}
	int getfail(char *s, int x, int n)
	{
		while (s[n - len[x] - 1] != s[n]) // 比较x节点回文串新建两端是否相等
			x = fail[x]; // 若不同, 再比较x后缀回文串两端
		return x;
	}
	void insert(char* s, int n)
	{
		for (int i = 0; i < n; i++)
		{
			int c = s[i] - 'a';
			int p = getfail(s, last, i);// 得到第i个字符可以加到哪个节点的两端形成回文串 
			if (!nxt[p][c])
			{
				tot++;
				len[tot] = len[p] + 2;  // 在p节点两端添加两个字符
				fail[tot] = nxt[getfail(s, fail[p], i)][c]; //tot点的后缀回文,可以由上一个节点的后缀回文尝试得到  
				sed[tot] = sed[fail[tot]] + 1; // 以当前节点为结尾的回文串个数
				nxt[p][c] = tot; // 新建节点
			}
			last = nxt[p][c]; // 当前节点成为上一个节点
			cnt[last]++; //当前节点回文串++
                        record[last] = i;
		}
	}
	void get_cnt()
	{
		for (int i = tot; i > 0; i--)
			cnt[fail[i]] += cnt[i];
		//fail[i] 的节点 为 i 节点的后缀回文串, 所以个数相加
	}
}pdt;
int main(){
	while (~scanf("%s", s))
	{
		pdt.init();
		pdt.build();
		pdt.insert(s, strlen(s));
		pdt.get_cnt();
	}
	return 0;
}

对于init,如果组数过多,可能会导致超时(例如 UVALive - 7041 The Problem to Slow Down You )

所以有一个newnode的版本,即每次新建节点时,再初始化信息。

struct Palindrome_tree
{
	int nxt[N][26];
	int fail[N];
	int len[N];
	int cnt[N];
	int sed[N];
	int tot;
	int last;
	void init()
	{
		tot = 0;
		memset(fail, 0, sizeof fail);
		memset(cnt, 0, sizeof cnt);
		memset(sed, 0, sizeof sed);
		memset(len, 0, sizeof len);
		memset(nxt, 0, sizeof nxt);
	}
	int newnode(int lenx)
	{
		for (int i = 0; i < 26; i++)
			nxt[tot][i] = 0;
		sed[tot] = cnt[tot] = 0;
		len[tot] = lenx;
		return tot;
	}
	void build()
	{
		tot = 0;
		newnode(0);
		tot = 1, last = 0;
		newnode(-1);
		fail[0] = 1;
	}
	int getfail(char* s, int x, int n)
	{
		while (s[n - len[x] - 1] != s[n])
			x = fail[x];
		return x;
	}
	void insert(char* s, int n)
	{
		for (int i = 1; i <= n; i++)
		{
			int c = s[i] - 'a';
			int p = getfail(s, last, i);
			if (!nxt[p][c])
			{
				tot++;
				newnode(len[p] + 2);
				fail[tot] = nxt[getfail(s, fail[p], i)][c];
				sed[tot] = sed[fail[tot]] + 1;
				nxt[p][c] = tot;
			}
			last = nxt[p][c];
			cnt[last]++;
		}
	}
	void get_cnt()
	{
		for (int i = tot; i > 0; i--)
			cnt[fail[i]] += cnt[i];
	}
}pdt;

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值