回文自动机常用操作和题目集

回文自动机


i i i为尾节点的字符串维护

增加一个节点后

即为在原有的所有真后缀回文串( m a r k [ f a i l [ n u m ] ] mark[fail[num]] mark[fail[num]]

增加一个回文串更新(mark[num]

if (!tree[last][in[i] - 'a']) {
	/*operation*/
	mark[num] = mark[fail[num]] + 1;
    //统计以该节点为结尾的回文串个数
}
//更新last
cnt[i]=mark[last];
回文自动机(PAM)

Interesting


本质不同的字符串总数

即为节点个数,抛去1号节点

mark[i]=num-1
Palindromes and Super Abilities
CA Loves Palindromic

维护本质不同的字符串(信息来自于子树)

对于一个本质不同的字符串

他的子树都可以访问他

标记所有字符串,统计他所有子树信息即可

由于编号越大,层数约深,所以倒着遍历即可

for (int i = 1; i <= n; i++) {
	while (in[i - len[last] - 1] != in[i]) last = fail[last];
	if (!tree[last][in[i] - 'a']) {
		/*operation*/
	}
	last = tree[last][in[i] - 'a'];
	mark[last]++;	//标记信息
}
for (int i = num; i; i--) {
	mark[fail[i]] += mark[i];
    //统计子树信息
}
P3649 回文串
拉拉队排练

维护本质不同的回文串(信息来自于父亲、祖先)

建立 f a i l fail fail

利用 d f s dfs dfs,回溯维护回文串祖先的信息

void build() {
	memset(head, 0, sizeof(int) * (num + 1)); tot = 0;
	for (int i = 2; i <= num; i++) AddEdge(fail[i], i);
    //建树,用fail边建树
}
int vis[maxn],res[maxn];
void dfs(int now) {
	vis[len[now]]++;//维护祖先信息
    /*operation 统计答案*/
	for (int i = head[now]; i; i = edge[i].next)
		dfs(edge[i].v);	//dfs
	vis[len[now]]--;//回溯
	return;
}
HDU6599 I Love Palindrome String
Palindromeness

维护信息有前后两端的回文串

建立两个回文串

一个正着建,一个倒着建

对于i节点维护i节点正序回文串信息和i+1节点的倒序回文串信息合并即可

struct PAM {
	int tree[maxn][26];
	int len[maxn], fail[maxn];
	int last, num , mark[maxn];
	void insert(char* in, int n) {
		/*operation*/
	}
}pre, suf;
int main() {
	scanf("%s", in + 1); in[0] = '#';
	int n = strlen(in + 1);
	pre.insert(in, n);
    //正向建立
	reverse(in + 1, in + 1 + n);
	suf.insert(in, n);
    //反向建立
	int ans = 0;
	for (int i = 1; i < n; i++) {
		ans = max(ans, pre.mark[i] + suf.mark[n-i]);
        //两个串信息合并
	}
}
P4287 双倍回文
Harry and magic string

前端添加节点

pre,last两个标记维护前后添加

pre为例,当添加节点后

新增的节点后最长回文串不是本身时,新添加的节点不会影响last

l e n [ l a s t ] , f a i l [ l a s t ] len[last],fail[last] len[last]fail[last]为正确的

新增的节点后,本身为最长回文串时,要更新last

更新方法为将 l a s t = p r e last=pre last=pre

由于只统计一边,所以统计不同本质字符串个数和总共字符串个数是正确的

但维护其他信息的时候需要认真思考

以下为向前插入:

向后插入类似

void pre_insert(char* in, int i) {	//向前添加方法
	while (in[i + len[pre] + 1] != in[i]) pre = fail[pre];
	if (!tree[pre][in[i] - 'a']) {
		len[++num] = len[pre] + 2;
		int j = fail[pre];
		while (in[i + len[j] + 1] != in[i]) j = fail[j];
		fail[num] = tree[j][in[i] - 'a'];
		tree[pre][in[i] - 'a'] = num;
		mark[num] = mark[fail[num]] + 1;	//统计个数
	}
	pre = tree[pre][in[i] - 'a'];
	if (len[pre] == stdr - stdl + 1) last = pre;//当本身变为回文串,更新last
	sum = sum + mark[pre];	//维护总个数
}
Victor and String

前向星优化空间

MLE时,我们可以使用链式前向星优化

即使用链式前向星创建字典树

查询:遍历所有边,由于每个点在字典树访问次数有限,所以复杂不会超

赋值:即为建边

struct Edge{
	int v;
	int w;	//w为字母的值
	int next;
}edge[maxn];
int head[maxn], tot;
inline void AddEdge(int u, int v, int w) {
	edge[++tot].v = v;
	edge[tot].w = w;
	edge[tot].next = head[u];
	head[u] = tot;
}
int find(int now, int w) {	//寻找函数
	for (int i = head[now]; i; i = edge[i].next)
		if (edge[i].w == w) return edge[i].v;
	return 0;
}
for (int i = 1; i <= n; i++) {
	while (in[i - len[last] - 1] != in[i]) last = fail[last];
	int treenode = find(last, in[i] - 'a');//寻找节点
	if (!treenode) {
		len[++num] = len[last] + 2;
		int j = fail[last];
		while (in[i - len[j] - 1] != in[i]) j = fail[j];
		fail[num] = find(j, in[i] - 'a');//寻找节点
		AddEdge(last, num, in[i] - 'a');//建边
		mark[num] = mark[fail[num]] + 1;
		treenode = num;	//更新节点
	}
	last = treenode;
	cnt[i] = mark[last];
}
CF17E Palisection
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值