zoukankan      html  css  js  c++  java
  • 回文自动机基础题

    LG1659 拉拉队排练

    n个女生从左到右排成一行,每个人手中都举了一个写有26个小写字母中的某一个的牌子

    如果连续的一段女生,有奇数个,并且他们手中的牌子所写的字母,从左到右和从右到左读起来一样,那么这一段女生就被称作和谐小群体.

    找出所有和谐小群体,并且按照女生的个数降序排序之后,前K个和谐小群体的女生个数的乘积是多少

    答案除以19930726的余数

    n ≤ 106, k ≤ 1012

    题解

    学习了一下回文自动机,推荐nianheng的博客。

    此题将状态将 len 排序后扫一遍就可以了。

    时间复杂度(O(n)),回文自动机的复杂度分析似乎跟AC自动机一样。

    注意写代码的时候新建节点要先处理信息再连边,否则 如果 last=1 的话 fail 可能会连向自己。

    #include<bits/stdc++.h>
    using namespace std;
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> T read(T&x){
    	return x=read<T>();
    }
    #define co const
    #define il inline
    typedef long long LL;
    
    co int mod=19930726;
    il int mul(int a,int b){
    	return (LL)a*b%mod;
    }
    il int fpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=mul(a,a))
    		if(b&1) ans=mul(ans,a);
    	return ans;
    }
    
    co int N=1000000+10;
    int last,tot;
    struct node{int ch[26],fa,len,siz;}s[N];
    char str[N];
    
    int get_fa(int x,int p){
    	while(str[p-s[x].len-1]!=str[p]) x=s[x].fa;
    	return x;
    }
    void extend(int p){
    	int cur=get_fa(last,p);
    	int now=s[cur].ch[str[p]-'a'];
    	if(!now){
    		now=++tot;
    		s[now].fa=s[get_fa(s[cur].fa,p)].ch[str[p]-'a'];
    		s[now].len=s[cur].len+2;
    		s[cur].ch[str[p]-'a']=now; // edit 1: last=1
    	}
    	++s[now].siz;
    	last=now;
    }
    il bool operator<(co node&a,co node&b){
    	return a.len>b.len;
    }
    int main(){
    	int n=read<int>();LL K=read<LL>();
    	scanf("%s",str+1);
    	last=tot=1;
    	s[1].len=-1,s[0].fa=s[1].fa=1;
    	for(int i=1;i<=n;++i) extend(i);
    	for(int i=tot;i>=2;--i) s[s[i].fa].siz+=s[i].siz;
    	sort(s+2,s+tot+1);
    	int ans=1;
    	for(int i=2;K;++i){
    		if(i>tot) {puts("-1");return 0;}
    		if(~s[i].len&1) continue;
    		if(s[i].siz<K){
    			K-=s[i].siz;
    			ans=mul(ans,fpow(s[i].len,s[i].siz));
    		}
    		else{
    			ans=mul(ans,fpow(s[i].len,K));
    			K=0;
    		}
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    LG5496 【模板】回文自动机(PAM)

    给定一个字符串 s。保证每个字符为小写字母。对于 s 的每个位置,请求出以该位置结尾的回文子串个数。

    这个字符串被进行了加密,除了第一个字符,其他字符都需要通过上一个位置的答案来解密。

    具体地,若第 i(i≥1) 个位置的答案是 k,第 i+1 个字符读入时的 ASCII 码为 c,则第 i+1 个字符实际的 ASCII 码为 (c−97+k) mod 26+97。所有字符在加密前后都为小写字母。

    对于 100% 的数据, 1≤∣s∣≤5×105

    题解

    PAM可以额外维护两个有用的信息:

    1. siz:表示节点 i 对应的回文串的出现次数
      siz 每次加入节点时+1,最后 fail 树上子树求和即可。
    2. num:表示节点 i 包含的本质不同的回文串个数
      num 每次加入节点时,num[i]=num[fail[i]]+1

    时间复杂度(O(n))

    #include<bits/stdc++.h>
    using namespace std;
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> T read(T&x){
    	return x=read<T>();
    }
    #define co const
    #define il inline
    typedef long long LL;
    
    co int N=500000+10;
    int last=1,tot=1;
    int ch[N][26],fa[N]={1,1},len[N]={0,-1},num[N];
    char str[N];
    
    int get_fa(int x,int pos){
    	while(str[pos-len[x]-1]!=str[pos]) x=fa[x];
    	return x;
    }
    int extend(int pos){
    	int p=get_fa(last,pos);
    	int x=ch[p][str[pos]-'a'];
    	if(!x){
    		x=++tot;
    		fa[x]=ch[get_fa(fa[p],pos)][str[pos]-'a'];
    		len[x]=len[p]+2;
    		num[x]=num[fa[x]]+1;
    		ch[p][str[pos]-'a']=x;
    	}
    	last=x;
    	return num[x];
    }
    int main(){
    	scanf("%s",str+1);int n=strlen(str+1);
    	int ans=0;
    	for(int i=1;i<=n;++i){
    		str[i]=(str[i]-97+ans)%26+97;
    		ans=extend(i);
    		printf("%d ",ans);
    	}
    	return 0;
    }
    

    别忘了开long long

    BZOJ2565 最长双回文串

    顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。

    输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串。

    题解

    正着倒着做一遍就完了。使用namespace很方便。

    #include<bits/stdc++.h>
    using namespace std;
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> T read(T&x){
    	return x=read<T>();
    }
    #define co const
    #define il inline
    typedef long long LL;
    
    co int N=100000+10;
    char s[N];
    
    namespace T1{
    	int last=1,tot=1;
    	int ch[N][26],fa[N]={1,1},len[N]={0,-1},ref[N];
    	
    	int get_fa(int x,int i){
    		while(s[i-len[x]-1]!=s[i]) x=fa[x];
    		return x;
    	}
    	void extend(int i){
    		int p=get_fa(last,i);
    		int x=ch[p][s[i]-'a'];
    		if(!x){
    			x=++tot;
    			fa[x]=ch[get_fa(fa[p],i)][s[i]-'a'];
    			len[x]=len[p]+2;
    			ch[p][s[i]-'a']=x;
    		}
    		ref[i]=x;
    		last=x;
    	}
    }
    namespace T2{
    	int last=1,tot=1;
    	int ch[N][26],fa[N]={1,1},len[N]={0,-1},ref[N];
    	
    	int get_fa(int x,int i){
    		while(s[i+len[x]+1]!=s[i]) x=fa[x];
    		return x;
    	}
    	void extend(int i){
    		int p=get_fa(last,i);
    		int x=ch[p][s[i]-'a'];
    		if(!x){
    			x=++tot;
    			fa[x]=ch[get_fa(fa[p],i)][s[i]-'a'];
    			len[x]=len[p]+2;
    			ch[p][s[i]-'a']=x;
    		}
    		ref[i]=x;
    		last=x;
    	}
    }
    
    int main(){
    	scanf("%s",s+1);int n=strlen(s+1);
    	for(int i=1;i<=n;++i) T1::extend(i);
    	for(int i=n;i>=1;--i) T2::extend(i);
    	int ans=0;
    	for(int i=1;i<n;++i)
    		ans=max(ans,T1::len[T1::ref[i]]+T2::len[T2::ref[i+1]]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    CF17E Palisection

    给你一个字符串,让你求出有多少对相交的回文子串。

    1 ≤ n ≤ 2·106

    题解

    正难则反,求相交会很难搞。把问题转化成求互不相交的回文子串再减一下就行了。

    不相交就正着倒着做一遍就行了。

    注意这题卡空间,于是我用unordered_map来搞。

    struct pairhash{
    	size_t operator()(CO pair<int,int>&x)CO{
    		return x.first^(~x.second);
    	}
    };
    
    CO int N=2000000+10;
    namespace PAM{
    	int str[N],n;
    	int last,tot;
    	tr1::unordered_map<pair<int,int>,int,pairhash> ch;
    	int len[N],fa[N],dep[N];
    	
    	void init(){
    		memset(str,-1,sizeof str),n=0;
    		last=tot=1;
    		len[0]=0,len[1]=-1,fa[0]=fa[1]=1;
    	}
    	int get_fail(int x){
    		while(str[n-len[x]-1]!=str[n]) x=fa[x];
    		return x;
    	}
    	void extend(int c){
    		str[++n]=c;
    		int p=get_fail(last);
    		if(!ch[make_pair(p,c)]){
    			int cur=++tot;
    			len[cur]=len[p]+2;
    			fa[cur]=ch[make_pair(get_fail(fa[p]),c)];
    			ch[make_pair(p,c)]=cur;
    			dep[cur]=dep[fa[cur]]+1;
    		}
    		last=ch[make_pair(p,c)];
    	}
    }
    
    char str[N];
    int pre[N];
    
    int main(){
    	int n=read<int>();
    	scanf("%s",str+1);
    	PAM::init();
    	for(int i=1;i<=n;++i){
    		PAM::extend(str[i]-'a');
    		pre[i]=add(pre[i-1],PAM::dep[PAM::last]);
    	}
    	int ans=(LL)pre[n]*(pre[n]-1)/2%mod;
    	PAM::init();
    	for(int i=n;i>=1;--i){
    		PAM::extend(str[i]-'a');
    		ans=add(ans,mod-mul(pre[i-1],PAM::dep[PAM::last]));
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    JSOI2013 快乐的jyy

    给定两个字符串A和B,表示JYY的两个朋友的名字。我们用A(i,j)表示A字符串中从第i个字母到第j个字母所组成的子串。同样的,我们也可以定义B(x,y)。

    JYY发现两个朋友关系的紧密程度,等于同时满足如下条件的四元组(i,j,x,y)的个数:

    1. 1<=i<=j<=|A| (这里|A|表示字符串A的长度)
    2. 1<=x<=y<=|B|
    3. A(i,j)=B(x,y)
    4. A(i,j)是回文串

    JYY希望你帮助他计算出这两个朋友之间关系的紧密程度。

    题解

    把两个串插入到同一个回文自动机里面 , 记录每一个回文串中两个字符串出现的次数 size[0/1].
    相乘就是一组回文串的贡献 , 答案就是 ∑size[i][0]∗size[i][1]

    插入第二个串的时候要把last设成1.

    #include<bits/stdc++.h>
    using namespace std;
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> T read(T&x){
    	return x=read<T>();
    }
    #define co const
    #define il inline
    typedef long long LL;
    
    co int N=50000+10;
    char s[N];
    int last=1,tot=1,T;
    int ch[N][26],fa[N]={1,1},len[N]={0,-1},siz[N][2];
    
    int get_fa(int x,int i){
    	while(s[i-len[x]-1]!=s[i]) x=fa[x];
    	return x;
    }
    void extend(int i){
    	int p=get_fa(last,i);
    	int x=ch[p][s[i]-'A'];
    	if(!x){
    		x=++tot;
    		fa[x]=ch[get_fa(fa[p],i)][s[i]-'A'];
    		len[x]=len[p]+2;
    		ch[p][s[i]-'A']=x;
    	}
    	++siz[x][T];
    	last=x;
    }
    int main(){
    	scanf("%s",s+1);int n=strlen(s+1);
    	for(int i=1;i<=n;++i) extend(i);
    	scanf("%s",s+1),n=strlen(s+1);
    	last=1,T=1;
    	for(int i=1;i<=n;++i) extend(i);
    	LL ans=0;
    	for(int i=tot;i>=2;--i){
    		siz[fa[i]][0]+=siz[i][0],siz[fa[i]][1]+=siz[i][1];
    		ans+=(LL)siz[i][0]*siz[i][1];
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    Antisymmetry

    对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
    现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

    N <= 500,000

    manacher

    manacher匹配的时候改一下匹配条件就可以了,只要两个数相加等于一时就可以,一定要注意是不存在奇数串的,而且统计的时候要用long long,还要除以二。

    时间复杂度(O(n))

    话说那个哈希+二分也可以过,利用一个反对称串长度一定为偶数,且后半段取反后形成回文串,不过实现和复杂度显然没有manacher更优。

    #include<bits/stdc++.h>
    #define rg register
    #define il inline
    #define co const
    template<class T>il T read()
    {
    	rg T data=0;
    	rg int w=1;
    	rg char ch=getchar();
    	while(!isdigit(ch))
    	{
    		if(ch=='-')
    			w=-1;
    		ch=getchar();
    	}
    	while(isdigit(ch))
    	{
    		data=data*10+ch-'0';
    		ch=getchar();
    	}
    	return data*w;
    }
    template<class T>il T read(rg T&x)
    {
    	return x=read<T>();
    }
    typedef long long ll;
    
    co int N=1e6+1;
    char s[N],a[N*2];
    int p[N*2];
    int n,m;
    
    void manacher()
    {
    	m=2*n+1;
    	for(int i=1;i<=n;++i)
    		a[i*2]=s[i],a[i*2+1]='#';
    	a[0]='+',a[1]='#',a[m+1]='-';
    	int mx=0,id=0;
    	for(int i=1;i<=m;i+=2)
    	{
    		if(mx>i)
    			p[i]=std::min(p[2*id-i],mx-i);
    		else
    			p[i]=1;
    		while(a[i+p[i]]-'0'+a[i-p[i]]-'0'==1||(a[i+p[i]]==a[i-p[i]]&&a[i+p[i]]=='#'))
    			++p[i];
    		if(i+p[i]>mx)
    			mx=i+p[i],id=i;
    	}
    }
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	read(n);
    	scanf("%s",s+1);
    	manacher();
    	ll ans=0;
    	for(int i=1;i<=m;i+=2)
    		ans+=(p[i]-1)/2;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    回文自动机

    因为我新学了PAM,所以这篇博客更新了。

    根本上来说只要把==改成!=即可,但是这样一来很多停止条件就没了,需要很多特判手动刹车,最后统计一下size即可

    但是!=是一个很弱的条件,改成相加为1更好。

    要去掉长度为奇数的回文串。方法是如果跳 p 跳到 1 了,直接 last=0;return;`即可。

    #include<bits/stdc++.h>
    using namespace std;
    template<class T> T read(){
    	T x=0,w=1;char c=getchar();
    	for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	return x*w;
    }
    template<class T> T read(T&x){
    	return x=read<T>();
    }
    #define co const
    #define il inline
    typedef long long LL;
    
    co int N=500000+10;
    char s[N];
    int last=1,tot=1;
    int ch[N][26],fa[N]={1,1},len[N]={0,-1},siz[N];
    
    int get_fa(int x,int i){
    	while(x!=1&&s[i-len[x]-1]-'0'+s[i]-'0'!=1) x=fa[x];
    	return x;
    }
    void extend(int i){
    	int p=get_fa(last,i);
    	if(p==1) {last=0;return;}
    	int x=ch[p][s[i]-'0'];
    	if(!x){
    		x=++tot;
    		int q=get_fa(fa[p],i);
    		if(q==1) fa[x]=0;
    		else fa[x]=ch[q][s[i]-'0'];
    		len[x]=len[p]+2;
    		ch[p][s[i]-'0']=x;
    	}
    	++siz[x];
    	last=x;
    }
    int main(){
    	int n=read<int>();
    	scanf("%s",s+1);
    	for(int i=1;i<=n;++i) extend(i);
    	int ans=0;
    	for(int i=tot;i>=2;--i){
    		ans+=siz[i];
    		siz[fa[i]]+=siz[i];
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Memcached缓存在.Net 中的使用(memcacheddotnet)
    转载 单目摄像机标定说明
    图像的上采样 下采样
    VOC数据集 目标检测
    tensorflow用pretrained-model做retrain
    图像分类基础
    TensorRT学习总结
    Jetson Nano Developer Kit
    pytorch深度学习60分钟闪电战
    系统检测工具ROSWTF
  • 原文地址:https://www.cnblogs.com/autoint/p/palindrome_automaton.html
Copyright © 2011-2022 走看看