题目描述
题解
貌似只有回文树的解法。
回文树
一种自动机,可以识别所有的回文串。
性质:一个串中本质不同的回文串最多有n个。
和其他自动机一样,它记录了一个ch数组,一个fail数组,fail数组在这里的指向是这个串的最长回文后缀。
在回文树中,每个节点都存了一个回文串,为了区分奇数串和偶数串,1->奇数0->偶数。
为了防止边界爆炸,len[1]=-1,fail[0]=1。
构造方法:仍是增量构造,我们可以维护一个变变量last,和后缀自动机一样,表示上一次的节点。
然后我们可以判断一下s[i-len[x]-1]==s[i]如果不满足,就跳last的fail,知道找到最长的last的后缀满足接上当前节点后是一个回文串。
然后我们新开节点cnt,len[cnt]=len[last]+2。
+2是因为我在last前后各添加了一个字符,所以要+2,。
然后就要连fail了,找到fail[last],和上面一样跳,然后直接连。
例题
APIO2014回文串,直接建出回文树,统计一下。

#include<iostream> #include<cstdio> #include<cstring> #define N 300002 using namespace std; typedef long long ll; ll ans; char s[N]; int n,tong[N],fail[N],len[N],ch[N][26],cnt,last; int main(){ scanf("%s",s+1);n=strlen(s+1); len[1]=-1;fail[0]=1;s[0]='&';cnt=last=1; for(int i=1;i<=n;++i){ int x=s[i]-'a'; while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][x]){ len[++cnt]=len[last]+2; int j=fail[last]; while(s[i-len[j]-1]!=s[i])j=fail[j]; fail[cnt]=ch[j][x]; ch[last][x]=cnt; } last=ch[last][x];++tong[last]; } for(int i=cnt;i>=2;--i){ tong[fail[i]]+=tong[i]; ans=max(ans,1ll*tong[i]*len[i]); } cout<<ans; return 0; }
SHOI2011双倍回文:对每一个节点求一个father,表示小于等于这个串一半的最长回文后缀,求法和求fail类似,然后统计一下(虽然我用的暴力加剪枝)。

#include<iostream> #include<cstdio> #define N 500002 using namespace std; int len[N],last,cnt,fail[N],ch[N][26],n,ans; char s[N]; int main(){ scanf("%d%s",&n,s+1); last=cnt=1;len[1]=-1;fail[0]=1; for(int i=1;i<=n;++i){ while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][s[i]-'a']){ len[++cnt]=len[last]+2; int x=fail[last]; while(s[i-len[x]-1]!=s[i])x=fail[x]; fail[cnt]=ch[x][s[i]-'a'];ch[last][s[i]-'a']=cnt; } last=ch[last][s[i]-'a']; } for(int i=cnt;i>=1;--i){ ///!!! if(len[i]%4)continue; if(len[i]<=ans)continue; int x=i; while(len[x]*2>len[i])x=fail[x]; if(len[x]*2==len[i])ans=max(ans,len[i]); } cout<<ans; return 0; }
然后看这道题。
先搞出来回文树。
考虑令dp[i]表示搞出来i这个回文串的最小代价、
然后我们在0的子树上转移,因为奇回文串不满足翻转性质。
dp[v]=dp[u]+1因为是0的子树,它是偶回文串,已经被翻过了,所以我们假装在翻之前就把这个字符填上去了。
dp[v]=dp[fa]+len[v]/2+1,我们在它的father上搞点事情,因为它的长度小于一半,所以我们把它补到一半后翻一下就好了。
那为什么不考虑前面的往后面翻的情况呢?
因为这两种情况代价相等。
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #define N 100002 using namespace std; queue<int>q; int ch[N][4],len[N],fail[N],n,t,last,trans[200],cnt,fa[N],dp[N],ans; char s[N]; int main(){ trans['A']=0;trans['T']=1;trans['C']=2;trans['G']=3; scanf("%d",&t); while(t--){ scanf("%s",s+1);n=strlen(s+1); last=cnt=1;fail[0]=1;len[1]=-1;s[0]='#'; for(int i=1;i<=n;++i){ int y=trans[s[i]]; while(s[i-len[last]-1]!=s[i])last=fail[last]; if(!ch[last][y]){ int x=fail[last]; len[++cnt]=len[last]+2; while(s[i-len[x]-1]!=s[i])x=fail[x]; fail[cnt]=ch[x][y];ch[last][y]=cnt; if(len[cnt]>2){ int tmp=fa[last]; while(s[i-len[tmp]-1]!=s[i]||(len[tmp]+2)*2>len[cnt])tmp=fail[tmp]; fa[cnt]=ch[tmp][y]; }else fa[cnt]=fail[cnt]; } last=ch[last][y]; } ans=n; for(int i=2;i<=cnt;++i)dp[i]=len[i]; dp[0]=1;dp[1]=0; q.push(0); while(!q.empty()){ int u=q.front();q.pop(); for(int i=0;i<4;++i)if(ch[u][i]){ int v=ch[u][i]; dp[v]=min(dp[v],dp[u]+1); int o=fa[v]; dp[v]=min(dp[v],dp[o]+len[v]/2-len[o]+1); ans=min(ans,dp[v]+n-len[v]); q.push(v); } } // for(int i=1;i<=cnt;++i)cout<<dp[i]<<" ";cout<<endl; printf("%d ",ans); for(int i=0;i<=cnt;++i){ fail[i]=0,fa[i]=0,len[i]=0; for(int j=0;j<4;++j)ch[i][j]=0; } } return 0; }