马拉车算法 && 回文树(回文自动机)

Manacher部分:

下标 i :0 是$ , 原字符串插入#字符变为 奇数长度,结尾位置添加@ 维持奇数字符个数

arr字符串: 经过处理的字符串 , eg -> fabbac  “$ # f # a # b # b # a # c # @”  

辅助数组p:p[i] 表示 arr字符串 在 i 位置的最长回文半径

两个关系:

  1. 最长回文串(是原串"fabbac"的最长回文串长度) =  p[i] - 1;
  2. 以 i 为中心的 回文串( arr串 ) 起始位置(索引) = ( i - p[i] ) / 2;

知道这些,再看代码就能秒懂了。

板子:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e6 + 7;
const int Inf = 1e9 + 7;
int N , M;
char s[Maxn] , t[Maxn];
int Manacher() {
	int lens = strlen(s);
    if (lens < 2) {
        return lens;
    }
    // 第一步:预处理,将原字符串转换为新字符串
    int n = -1;
    t[++n] = '$';
    for (int i = 0 ; i < lens ; i++) {
    	t[++n] = '#' , t[++n] = s[i];
    }
    t[++n] = '#' , t[++n] = '@';
    ++n;
    // 第二步:计算数组p、起始索引、最长回文半径
    int p[n];
    int id = 0, mx = 0;
    int maxLength = -1;
    int index = 0;
    for (int j=1; j j ? min(p[2*id-j], mx-j) : 1;
        // 向左右两边延伸,扩展右边界
        while (t[j+p[j]] == t[j-p[j]]) {
            p[j]++;
        }
        // 如果回文子串的右边界超过了mx,则需要更新mx和id的值
        if (mx < p[j] + j) {
            mx = p[j] + j;
            id = j;
        }
        // 如果回文子串的长度大于maxLength,则更新maxLength和index的值
        if (maxLength < p[j] - 1) {
            maxLength = p[j] - 1;
            index = j;
        }
    }
    return maxLength;
}

int main()
{
}

 

 

回文树部分

套用AC自动机思想,建立两颗树

0树 和 1树 , 前者表示偶数回文串,后者表示奇数回文串

通过fail指针操作可以在两棵树中 来回跳跃

具体内容 就看代码解释,可以很好地理解

主要是要知道

  • fail 指向当前now位置 前的最长回文后缀 的 回文后缀
  • last在运行过程中会一直改变,所以 S[ 1 ~ i ] 的本质不同的回文串 是 cnt - 1 个

还有一些点需要了解

我们计算给出的S串的 所有回文子串(包括重复的)的数目,有两种方法

  1. 第一种是用count函数,但是计算的时候0树 和1树 的cnt被重复计算了,所以只算一个就行。
    void count() {
        All = 0;//注意取模问题
        for(int i = cnt ; i >= 0 ; --i){
        	rcnt[fail[i]] = (rcnt[fail[i]] + rcnt[i]) % Mod;
        	if(i > 0)	All = (All + rcnt[i]) % Mod;
        }
    }
    

     

  2. 在for(1 ~ lens)的过程中 把每次的 num[last] 加到一个sum中即可。思考一下,这个方法不仅是计算所有回文子串数目的方法,也是计算 已插入字符集(可以是1 ~ i 或 n ~ i) 的回文子串数目——包括重复部分(再强调一下)。
    //sum[lens] 就是全部回文子串的数目
    sum[0] = 0;
    for(int i = 1 ; i <= lens ; ++i){
    	add(S[i]);
    	sum[i] = (sum[i-1] + (LL)num[last]) % Mod;
    }
    //_________________________________
    //sum[1]也是全部回文子串的数目
    sum[lens+1] = 0;
    for(int i = lens ; i >= 1 ; --i){
    	add(S[i]);
    	sum[i] = (sum[i+1] + (LL)num[last]) % Mod;
    }
    

     

当这个模板处理字符串过长时可能会MLE,这时候就需要一些处理,或者直接考虑放弃此算法,改用manacher了,因为我们改用链表模拟时,如果要处理的字符串中字符范围是100,很可能会TLE,当然只有大小写字母是能满足的。

用下面第一个模板1 在2e6 * 26时就会MLE , 改用模板2 可过,当然速度稍慢一些,毕竟常数大了点。

 

模板1(数组模拟版)

const int Maxn = 5e5 + 7;
const int Inf = 1e9 + 7;
const int Over = 26;

int N , M;
struct Plt{
	char str[Maxn];
	int lens;

	int nxt[Maxn][Over];
	int fail[Maxn];
	int cnt[Maxn];
	int num[Maxn];
	int len[Maxn];
	int S[Maxn];
	int last;
	int n;
	int p;
 
	int newnode(int l) {
		for (int i = 0 ; i < Over ; ++ i) nxt[p][i] = 0;
		cnt[p] = 0;
		num[p] = 0;
		len[p] = l;
		return p++;
	}
 
	void init() {
		p = 0;
		newnode(0);
		newnode(-1);
		last = 0;
		n = 0;
		S[n] = -1;
		fail[0] = 1;
	}
 
	int get_fail(int x) {
		while(S[n - len[x] - 1] != S[n]) x = fail[x];
		return x;
	}
 
	void add(int c) {
		c -= 'a';
		S[++n] = c;
		int cur = get_fail(last);
		if (!nxt[cur][c]) {
			int now = newnode(len[cur] + 2);
			fail[now] = nxt[get_fail(fail[cur])][c];
			nxt[cur][c] = now;
			num[now] = num[fail[now]] + 1;
		}
		last = nxt[cur][c];
		cnt[last]++;
	}
 
	void count() {
		for (int i = p - 1 ; i > 0 ; --i){
			cnt[fail[i]] += cnt[i];
		}
	}

	void Buildtree(){
		for(int i = 0 ; i < lens ; ++i){
			add(str[i]);
		}
	}

	void solve(){
		lens = strlen(str);
		init();
		Buildtree();
		count();
	}
};

模板2(链表版)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 2e6 + 7;
const int Inf = 1e9 + 7;
const int Over = 26;
const LL Mod = 51123987;
int N , M;
struct link //next的邻接表
{
    int u[Maxn+5];int v[Maxn+5];
    int next[Maxn+5];int head[Maxn+5];
    int tot;
    void clear()
    {
        memset(head,-1,sizeof(head));
        tot=0;
    }
    void clear(int x){head[x]=-1;}
    int get(int x,int y)
    {
        for(int i=head[x];i!=-1;i=next[i])
        {
            if(u[i]==y)
                return v[i];
        }
        return 0;
    }
    void insert(int x,int y,int z)
    {
        u[tot]=y;  v[tot]=z;
        next[tot]=head[x];
        head[x]=tot++;
    }

};
struct plt{
	char S[Maxn];
	int ss[Maxn];
	int n;
	int fail[Maxn];
	int len[Maxn];
	int num[Maxn];
	int cnt[Maxn];
	int last;
	int p;
	link next;

	int lens;
    int new_node(int x) {
        cnt[p]=0;
        next.clear(p);
        num[p]=0;
        len[p]=x;
        return p++;
    }

	void init() {
		next.clear();
		p=0;
		new_node(0);
		new_node(-1);
		last=0;
		n=0;
		ss[0]=-1;
		fail[0]=1;
	}

	void Input(){
		scanf(" %s",S+1);
		lens = strlen(S+1);
	}
	int get_fail(int x){
		while(ss[n-len[x]-1] != ss[n])	x=fail[x];
		return x;
	}
	void add(int x){
		x-='a';
		ss[++n]=x;
		int cur=get_fail(last);
		if(!(last=next.get(cur,x)))
		{
			int now=new_node(len[cur]+2);
			fail[now]=next.get(get_fail(fail[cur]),x);
			next.insert(cur,x,now);
			num[now]=num[fail[now]]+1;
			last=now;
		}
		cnt[last]++;
	}
	void Buildtree(){
		for(int i = 1 ; i <= lens ; ++i){
			add(S[i]);
		}
	}

	void count() {
		for(int i = p-1 ; i >= 0 ; --i){
			cnt[fail[i]] += cnt[i];
		}
	}
	void solve(){
		Input();
		init();
		Buildtree();
		count();
	}
};
int main()
{
}

 

 

题目补充:

洛谷 4555

洛谷1659

(下面的https://vjudge.net/contest/321569 密码:rushb)

HYSBZ - 3676

URAL - 1960

UVALive - 7041

CodeForces - 17E

HDU - 3948

题解:

https://blog.csdn.net/castomere/article/details/100081064

你可能感兴趣的:(数据结构)