先整理各路大神的题解 Orz,以后再埋坑
SP7586 NUMOFPAL - Number of Palindromes
Description
求一个串中包含几个回文串。
Input
输入一个字符串S
Output
包含的回文串的个数.
思路一:
用马拉车求出预处理后以每个字母处的回文半径P[i],遍历一遍,ans=ans+P[i]/2,最终ans就是答案
答案是以每一位为中心的回文串长度/2的和,(如果添加字符则为回文半径长度/2。)
不能理解的话,可以看下这个
# a # a # b # a # a #
1 2 3 2 1 6 1 2 3 2 1
数字对应于每一个位置的回文半径
.(实际上减去1才是,但是计算的时候要加上1,所以代码里面直接用了这个.)
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #include<cstring> 5 #define R register 6 7 using namespace std; 8 9 const int maxn=1008; 10 11 char ch[maxn]; 12 char s[maxn<<2]; 13 int len,RL[maxn<<2],MaxRight,center,ans; 14 int main() 15 { 16 scanf("%s",ch); 17 len=strlen(ch); 18 for(R int i=0;i<len;i++)s[2*i+1]=ch[i]; 19 len=2*len+1; 20 for(R int i=0;i<len;i++) 21 { 22 if(i<=MaxRight) 23 RL[i]=min(RL[2*center-i],MaxRight-i); 24 else RL[i]=1; 25 while(s[i-RL[i]]==s[i+RL[i]] and i-RL[i]>=0 and i+RL[i]<len) 26 RL[i]++; 27 if(i+RL[i]-1>MaxRight) 28 MaxRight=i+RL[i]-1,center=i; 29 } 30 for(R int i=0;i<len;i++)ans+=RL[i]/2; 31 printf("%d",ans); 32 }
我自己写的代码:
1 #include <iostream> 2 #include <string> 3 #include <string.h> 4 #include <algorithm> 5 #include <vector> 6 7 using namespace std; 8 int sum; 9 10 void Manacher(string s) 11 { 12 sum=0; 13 /*改造字符串*/ 14 string res="$#"; 15 for(int i=0;i<s.size();++i) 16 { 17 res+=s[i]; 18 res+="#"; 19 } 20 21 /*数组*/ 22 vector<int> P(res.size(),0); 23 int mi=0,right=0; //mi为最大回文串对应的中心点,right为该回文串能达到的最右端的值 24 int maxLen=0,maxPoint=0; //maxLen为最大回文串的长度,maxPoint为记录中心点 25 26 for(int i=1;i<res.size();++i) 27 { 28 //关键句,文中对这句以详细讲解 29 if(right>i) 30 P[i]=min(P[2*mi-i],right-i); 31 else 32 P[i]=1; 33 // P[i]=right>i ?min(P[2*mi-i],right-i):1; 34 35 while(res[i+P[i]]==res[i-P[i]]) 36 ++P[i]; 37 38 if(right<i+P[i]) //超过之前的最右端,则改变中心点和对应的最右端 39 { 40 right=i+P[i]; 41 mi=i; 42 } 43 44 if(maxLen<P[i]) //更新最大回文串的长度,并记下此时的点 45 { 46 maxLen=P[i]; 47 maxPoint=i; 48 } 49 sum+=P[i]/2; 50 } 51 // return s.substr((maxPoint-maxLen)/2,maxLen-1); //要返回最长子串就把函数类型改为string 52 printf("%d ",sum); 53 } 54 int main() 55 { 56 string str; 57 cin>>str; 58 Manacher(str); 59 }
思路二:
回文自动机板子题,每次在LAST上CNT++,,最后从父亲累加儿子的CNT,因为如果fa[v]=u,则u一定是v的子回文串; 统计答案
1 #include<set> 2 #include<map> 3 #include<ctime> 4 #include<queue> 5 #include<cmath> 6 #include<cstdio> 7 #include<vector> 8 #include<cstring> 9 #include<cstdlib> 10 #include<iostream> 11 #include<algorithm> 12 #define ll unsigned long long 13 #define mem(a,b) memset(a,b,sizeof(a)) 14 #define mec(a,b) memcpy(a,b,sizeof(b)) 15 using namespace std; 16 const int MAXN=1000+50; 17 char str[MAXN]; 18 int len; 19 ll ans; 20 int hd[MAXN],ect; 21 struct Edge{int nx,to,w;}ar[MAXN]; 22 void adds(int u,int v,int w){ar[++ect].to=v,ar[ect].w=w,ar[ect].nx=hd[u],hd[u]=ect;} 23 struct PAM 24 { 25 int tot,last,n; 26 int fa[MAXN],ln[MAXN],s[MAXN],cnt[MAXN]; 27 void init() 28 { 29 fa[0]=fa[1]=1; 30 ln[tot=1]=-1;ln[last=n=ect=0]=0; 31 s[0]=-1; 32 } 33 int ge(int u,int w) 34 {for(int e=hd[u],v=ar[e].to;e;v=ar[e=ar[e].nx].to)if(ar[e].w==w)return v;return 0;} 35 int gf(int k){while(s[n-ln[k]-1]^s[n])k=fa[k];return k;} 36 inline void extend(int x,int c) 37 { 38 s[++n]=c;last=gf(last); 39 if(!ge(last,c)) 40 { 41 fa[++tot]=ge(gf(fa[last]),c); 42 ln[tot]=ln[last]+2; 43 adds(last,tot,c); 44 45 } 46 cnt[last=ge(last,c)]++; 47 } 48 void calc() 49 { 50 for(int i=tot;i>=2;i--) 51 { 52 cnt[fa[i]]+=cnt[i]; 53 ans+=cnt[i]; 54 } 55 } 56 }pam; 57 int main() 58 { 59 60 pam.init(); 61 scanf("%s",str+1);len=strlen(str+1); 62 //for(int i=1;i<=len;i++)printf("%c",str[i]); 63 for(int i=1,x;i<=len;i++)pam.extend(i,str[i]-'a'); 64 pam.calc(); 65 printf("%lld ",ans); 66 return 0; 67 68 }
思路三:
利用回文树来写,回文树是一种处理回文串的强大工具
推荐学习博客:
回文树介绍(Palindromic Tree)
1 #include <iostream> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <algorithm> 5 #include <math.h> 6 #include <stdio.h> 7 8 using namespace std; 9 #define MAX 1005 10 struct Node 11 { 12 int next[26]; 13 int len; 14 int sufflink; 15 int num; 16 }tree[MAX]; 17 char s[MAX]; 18 19 int num; 20 int suff; 21 bool addLetter(int pos) 22 { 23 int cur=suff,curlen=0; 24 int let=s[pos]-'a'; 25 26 while(1) 27 { 28 curlen=tree[cur].len; 29 if(pos-1-curlen>=0&&s[pos-1-curlen]==s[pos]) 30 break; 31 cur=tree[cur].sufflink; 32 } 33 if(tree[cur].next[let]) 34 { 35 suff=tree[cur].next[let]; 36 return false; 37 } 38 num++; 39 suff=num; 40 tree[num].len=tree[cur].len+2; 41 tree[cur].next[let]=num; 42 if(tree[num].len==1) 43 { 44 tree[num].sufflink=2; 45 tree[num].num=1; 46 return true; 47 } 48 while(1) 49 { 50 cur=tree[cur].sufflink; 51 curlen=tree[cur].len; 52 if(pos-1-curlen>=0&&s[pos-1-curlen]==s[pos]) 53 { 54 tree[num].sufflink=tree[cur].next[let]; 55 break; 56 } 57 } 58 tree[num].num=1+tree[tree[num].sufflink].num; 59 return true; 60 61 } 62 void initTree() 63 { 64 num=2;suff=2; 65 tree[1].len=-1;tree[1].sufflink=1; 66 tree[2].len=0;tree[2].sufflink=1; 67 } 68 int main() 69 { 70 scanf("%s",s); 71 int len=strlen(s); 72 initTree(); 73 long long int ans=0; 74 for(int i=0;i<len;i++) 75 { 76 addLetter(i); 77 ans+=tree[suff].num; 78 } 79 printf("%d ",ans); 80 return 0; 81 }
看看上一题的升级版
The Number of Palindromes
题意:
给出一个字符串,求其不相同回文子串的个数
思路一:
回文自动机模板题,因为回文自动机中每个新建的结点就表示一个回文子串,各个结点都不相同
所以不同回文子串个数就是回文自动机中新增结点个数,直接输出即可
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int MAXN = 100005 ; 5 const int N = 26 ; 6 typedef long long LL; 7 struct Palindromic_Tree { 8 int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成 9 int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点 10 LL cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的) 11 int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数 12 int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串) 13 int S[MAXN] ;//存放添加的字符 14 int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。 15 int n ;//表示添加的字符个数。 16 int p ;//表示添加的节点个数。 17 18 int newnode ( int l ) {//新建节点 19 for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ; 20 cnt[p] = 0 ; 21 num[p] = 0 ; 22 len[p] = l ; 23 return p ++ ; 24 } 25 26 void init () {//初始化 27 p = 0 ; 28 newnode ( 0 ) ; 29 newnode ( -1 ) ; 30 last = 0 ; 31 n = 0 ; 32 S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判 33 fail[0] = 1 ; 34 } 35 36 int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的 37 while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ; 38 return x ; 39 } 40 41 void add ( int c ) { 42 c -= 'a' ; 43 S[++ n] = c ; 44 int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置 45 if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串 46 int now = newnode ( len[cur] + 2 ) ;//新建节点 47 fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转 48 next[cur][c] = now ; 49 num[now] = num[fail[now]] + 1 ; 50 } 51 last = next[cur][c] ; 52 cnt[last] ++ ; 53 } 54 55 void count () { 56 for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ; 57 //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串! 58 } 59 }T; 60 61 int main() 62 { 63 std::ios::sync_with_stdio(false); 64 string a; 65 int t; 66 int ct=1; 67 cin>>t; 68 while(t--) 69 { 70 cin>>a; 71 T.init(); 72 int len=a.size(); 73 for(int i=0;i<len;i++) 74 T.add(a[i]); 75 cout<<"Case #"<<ct++<<": "; 76 cout<<T.p-2<<endl; //输出新增结点个数即可 77 } 78 }
思路二:
后缀数组+马拉车(本质不同回文子串统计)
回文子串可以考虑先来个O(n)的马拉车预处理,这样每个回文子串长度必然是计数,那么我们可以统计本质不同的(正中间的字符+右半边串)回文子串个数。然后可以考虑用后缀自动机统计答案。这道题的关键的关键在于去重的处理。去重要求去掉:h[i]范围内已经被统计过的串。那么可以用一个变量维护 目前已经被统计过的长度。要注意到h数组和马拉车的lc数组是没什么关系的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define rank rk 4 const int MAX = 2e5+10000; 5 char ch[MAX]; 6 int cntA[MAX],cntB[MAX],A[MAX],B[MAX],tsa[MAX],rank[MAX],SA[MAX],lc[MAX],h[MAX]; 7 int n,t; 8 int Cas =1; 9 void init(){ 10 memset(ch,0,sizeof ch); 11 ch[0]='z'+1; 12 } 13 void input(){ 14 scanf("%s",ch+1); 15 n = strlen(ch+1); 16 ch[n*2+1]='#'; 17 for (int i=n;i>=1;i--){ 18 ch[i*2] = ch[i]; 19 ch[i*2-1] ='#'; 20 } 21 n = n*2+1; 22 ch[n+1]='