传送门 http://acm.hdu.edu.cn/showproblem.php?pid=6599
题意:
给你一个长度为 \(n\) 的字符串, 现在问你长度为 \(1,2\dots n\) 的子串中, 有多少个子串 \(str\) 是回文串, 且它的长度缩短到 $\lceil \frac{|str|}{2} \rceil $ 后也是回文串.
分析:
很明显我们可以分别利用回文树中的 \(cnt\) 数组和 \(fail\) 数组, 把任意长度为 \(len\) 的字符串的回文子串和它的最长回文后缀在 \(\mathcal{O}(n)\) 的时间处理出来. 知道如何去求出上述的东西之后, 我们只需要考虑如何去剔除一些不合法的回文子串.
对于一个回文串 \(str\), 想让他的前一半回文相当于让他后一半回文, 那么如果他的最长回文后缀长度小于 \(\frac{|str|}{2}\), 显然是不行的, 所以考虑最长回文后缀长度大于等于 \(\frac{|str|}{2}\) 的情况. 我们发现如果想让它的后一半回文, 那么就是要缩短删掉若干个原串和最长回文后缀的差, 因此我们只需要判断 $\lfloor \frac{|str|}{2} \rfloor$ 是否能够整除 \((|str|-|str_{maxsuffix}|)\) 即可
代码:
- #include <bits/stdc++.h>
- #define maxn 300005
- using namespace std;
- char str[maxn];
- typedef long long ll;
- struct PAM{// 回文树
- int next[maxn][26],fail[maxn],len[maxn],cnt[maxn],S[maxn];
- int id,n,last;
- int newnode(int x){
- for(int i=0;i<26;i++){
- next[id][i]=0;
- }
- cnt[id]=0;
- len[id]=x;
- return id++;
- }
- void init(){
- id=0;
- newnode(0);
- newnode(-1);
- fail[0]=1;
- S[0]=-1;
- last=n=0;
- }
- int getfail(int x){
- while(S[n-len[x]-1]!=S[n]) x=fail[x];
- return x;
- }
- void Insert(int c){
- c-='a';
- S[++n]=c;
- int cur=getfail(last);
- if(!next[cur][c]){
- int now=newnode(len[cur]+2);
- fail[now]=next[getfail(fail[cur])][c];
- next[cur][c]=now;
- }
- last=next[cur][c];
- cnt[last]++;
- }
- void getsum(){// 自下向上更新
- for(int i=id-1;i>=0;i--){
- cnt[fail[i]]+=cnt[i];
- }
- }
- }pam;
- int Ans[maxn];
- bool judge(int x,int Len){
- int tmp1=pam.len[pam.fail[x]];
- int tmp2=pam.len[x]-tmp1;
- if((pam.len[x]/2)%tmp2==0) return true;
- else return false;
- }
- int main()
- {
- while(~scanf("%s",str)){
- pam.init();
- int len=strlen(str);
- for(int i=0;i<=len;i++){
- Ans[i]=0;
- }
- for(int i=0;i<len;i++){
- pam.Insert(str[i]);
- }
- pam.getsum();
- ll res=0;
- for(int i=2;i<pam.id;i++){
- if(judge(i,(pam.len[i]+1)/2)){
- Ans[pam.len[i]]+=pam.cnt[i];
- }
- }
- for(int i=1;i<=len;i++){
- if(i==1) printf("%d",Ans[i]);
- else printf("%d",Ans[i]);
- }
- puts("");
- }
- return 0;
- }
来源: http://www.bubuko.com/infodetail-3133506.html