PAM学习笔记

1 篇文章 0 订阅
1 篇文章 0 订阅

Manacher

按照惯例,先放上 m a n a c h e r manacher manacher(其实是为了给自己看。。。
首先为了处理奇数串和偶数串的问题,我们可以给两个字符之间插入一些特殊字符。
p [ i ] p[i] p[i]为以 i i i为中心最长回文串长度。
m x mx mx为当前 i + p [ i ] i+p[i] i+p[i]的最大值, i d id id m x mx mx i i i
假设当前加入一个新节点 i i i,如果 i &lt; = m x i&lt;=mx i<=mx,那么说明他处在以 i d id id为中心的回文串中,此时必定有一个以 i d id id为中心的对应节点,那么我们从这个位置直接继承 p [ i ] p[i] p[i],然后暴力往外拓展。
否则直接暴力往外拓展。
考虑时间复杂度的证明。
加入不在镜像中,往外拓展的一定是之前没拓展过的。
在这里插入图片描述
加入在镜像中,我们考虑能扩展的情况肯定只有
在这里插入图片描述
也就是说只有 i ′ i&#x27; i拓展超过了 i d id id的左端点时, i i i才有可能会拓展,否则就会跟 i ′ i&#x27; i一模一样,我们注意到黄色那段是没有拓展过的。
也就是说,每次拓展只会拓展未拓展过的,所以时间复杂度是线性的。


PAM

也就是回文自动机,我们用一个节点表示一个回文串。
其实这个东西跟 A C AC AC自动机类似,他有两颗字符树,一棵是代表偶数长度回文串的树,一棵是代表奇数长度回文串的树,一棵以 0 0 0作为根节点,长度为 0 0 0,一棵以 1 1 1为根节点,长度为 − 1 -1 1,特殊的 0 0 0 f a i l fail fail 1 1 1
我们要维护的信息有:
l e n [ i ] len[i] len[i]: i i i这个节点所代表的回文串的长度。
f a i l [ i ] fail[i] fail[i]: i i i这个节点所代表的回文串的最长后缀回文串。
c n t [ i ] cnt[i] cnt[i]: i i i这个节点所代表的回文串的出现次数。
c h [ i ] [ c ] ch[i][c] ch[i][c]: i i i这个节点往外加一个字符 c c c所产生的新回文串。
l a s t last last:与后缀自动机的 l a s t last last类似,表示上一个添加的字母所产生的回文串所代表的节点。

每次添加一个点我们直接在 l a s t last last f a i l fail fail即可,具体过程跟 A C AC AC自动机是一样的。

那么如何证明时间复杂度呢。
首先根据 m a n a c h e r manacher manacher的时间复杂度的证明,每次只有往外拓展时才会产生新的不同的回文串,而总拓展次数是线性的,所以回文自动机节点也是线性的。
然后我们考虑 f a i l fail fail指针的移动为什么是线性的,每一个 f a i l fail fail指针注意到他只会失配一次,而每一个节点也只会成功匹配一次,所以总的时间复杂度是线性的。


code:BZOJ3676: [Apio2014]回文串

#include <ctime>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
 
using namespace std;
typedef long long LL;
LL _max(LL x, LL y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const int N = 300001;
int read() {
    int s = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * f;
}
 
struct PAM {
	char ss[N];
    int cnt, last, len[N], s[N], fail[N], son[26][N];

	int new_node(int L) {
	    len[++cnt] = L;
	    return cnt;
	}
	
	int get_fail(int now, int n) {
	    while(ss[n - len[now] - 1] != ss[n]) 
	    now = fail[now];
	    return now;
	}
	
	void ins(int c, int i) {
        int hh = get_fail(last, i);
        if(!son[c][hh]) {
            int now = new_node(len[hh] + 2);
            fail[now] = son[c][get_fail(fail[hh], i)];
            son[c][hh] = now;
        } ++s[last = son[c][hh]];
	}
	
	void bt() {
	    cnt = -1; new_node(0), new_node(-1);
	    last = 0; fail[0] = 1; int u = strlen(ss + 1);
	    for(int i = 1; i <= u; i++) ins(ss[i] - 'a', i);
	}
	
	LL getmx() {
		LL maxx = 0;
    	for(int i = cnt; i >= 1; i--) s[fail[i]] += s[i], maxx = _max(maxx, (LL)s[i] * len[i]);
    	return maxx;
	}
} t;

int main() {
    scanf("%s", t.ss + 1); t.ss[0] = -1;
    t.bt();
    printf("%lld\n", t.getmx());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值