• 省选算法学习-回文自动机 && 回文树


    前置知识

    首先你得会manacher,并理解manacher为什么是对的(不用理解为什么它是$O(n)$,这个大概记住就好了,不过理解了更方便做$PAM$的题)

    什么是回文自动机?

    回文自动机(Palindrome Automaton),是一类有限状态自动机,能识别一个字符串的所有回文子串

    它可简化构建出回文树

    回文自动机的构造

    网上资料很多,不拿出来一步步说了,说一下数组意义、放个板子

    $len[u]$表示节点$u$代表的回文串的长度

    $ch[u][c]$代表在$u$的回文串两端添加字符$c$得到的新回文串节点

    $fail[u]$表示节点$u$的回文串的最长的回文后缀所在的节点

    char s[300010];
    int n;
    //这个板子里面的num表示当前节点的回文串出现了几遍
    namespace pam{
    	int fail[300010],num[300010],len[300010],ch[300010][26],last,cnt;
    	inline int newnode(int w){len[++cnt]=w;return cnt;}
    	void init(){
    		s[0]=-1;cnt=-1;fail[0]=1;last=0;
    		newnode(0);newnode(-1);//插入两个根节点,设置fail
    	}
    	inline int getfail(int cur,int pos){
    		while(s[pos-len[cur]-1]!=s[pos]) cur=fail[cur];//跳fail
    		return cur;
    	}
    	void insert(int x){
    		int c=(s[x]-'a'),cur=getfail(last,x);
    		if(!ch[cur][c]){//新建节点
    			int now=newnode(len[cur]+2);
    			fail[now]=ch[getfail(fail[cur],x)][c];
    			ch[cur][c]=now;
    		}
    		num[last=ch[cur][c]]++;
    	}
    }
    

    几个容易写错的点:

    fail初始化的时候,如果多组数据并且在newnode里面初始化信息,那么在init的时候记得把fail放到newnode后面

    插入的时候函数传进去的是位置

    newnode是len[cur]+2不是+1

    一些拓展

    节点上

    首先显然可以统计这个节点的回文串出现次数

    统计次数的时候还要加上$fail$树上子树内的所有节点的出现次数

    对于一类回文串拥有某个和其$fail$树有关的性质的题目,可以记录一个$trans$,和跳$fail$一样跳,最后用$bfs$来做$dp$或者递推

    关于求最小回文串分解

    这个问题是问可以把一个字符串分解成最少多少个回文串

    解决的方法:

    考虑一个显然的$dp$:$dp[i]=dp[j-1]+1 (s[i...j]=palindrome)$

    记录一个$anc[u]$

    如果$len[u]-len[fail[u]]==len[fail[u]]-len[fail[fail[u]]]$,那么$anc[u]=anc[fail[u]]$

    否则$anc[u]=u$

    对于$u$的所有跳上去的$anc$集合$S$,我们发现,这个集合中的元素构成一个等差数列,相邻的两项差代表一种从当前点前面递推到当前点的回文串长度

    对于每个回文树节点记录$tmp[u]=min(tmp[i-len[anc[u]],tmp[fail[u]])$,然后用这个$tmp[u]+1$来更新当前节点的$dp$,然后$u=fail[anc[u]]$往上跳,直到到达根

    证明网上有论文,这里放个代码

    inline void insert(int x){
    	int c=s[x]-'a',cur=getfail(last,x);
    	val[x]=1e9;
    	if(!ch[cur][c]){
    		int now=newnode(len[cur]+2);
    		fail[now]=ch[getfail(fail[cur],x)][c];
    		ch[cur][c]=now;
    		anc[now]=((fail[now]>1&&len[now]-len[fail[now]]==len[fail[now]]-len[fail[fail[now]]])?anc[fail[now]]:now);
    	}
    	last=ch[cur][c];
    	for(cur=ch[cur][c];cur>1;cur=fail[anc[cur]]){
    		tval[cur]=val[x-len[anc[cur]]];
    		tpos[cur]=x-len[anc[cur]];
    		if(anc[cur]!=cur&&tval[fail[cur]]<tval[cur]) tval[cur]=tval[fail[cur]],tpos[cur]=tpos[fail[cur]];
    		if(val[x]>tval[cur]+1) val[x]=tval[cur]+1,pos[x]=tpos[cur];
    	}
    }
    
  • 相关阅读:
    JavaScript 获取CSS媒体查询信息
    拖拉事件
    JavaScript 中的正常任务与微任务
    IOS 采用https 协议访问接口
    将类数组 转化为数组
    合并两个数组的方法
    base64转码
    Promise 异步执行的同步操作
    proxy set 拦截
    VIm 一些常用的设置
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/10148909.html
Copyright © 2020-2023  润新知