题意

求一个字符串中本质不同的回文子串的个数。

$ 1\leq |string| \leq 100000$

思路

好像是回文自动机的裸题,但是可以用 \(\text{Manacher}\) (马拉车)算法配合后缀数组(或配合哈希表)解决。

\(\text{Manacher}\) 算法非常短小精悍,它可以在线性时空内求出以每个点为中心拓展的最远距离,筛出与 \(n\) 同阶个数个回文串,这些回文串包含原串所有本质不同的回文串。

为了判掉奇偶串的问题,我们为字符串穿插一个特殊字符,如字符串 abccba,就变成了 #a#b#c#c#b#a#。

设每个点向两边拓展形成的最远距离(包含本身)为数组 \(p\) ,那么上述字符串对应的 \(p\) 数组如下:

str # a # b # c # c # b # a #
p 1 2 1 2 1 2 7 2 1 2 1 2 1

不难看出,\(\max\{p\}-1\) 就是原串的最长回文串长度,同理每个字符向两边拓展的最远距离也可以得到。

算法流程也比较简短,首先维护两个变量,\(mr(\text{max right})\) 和 \(pos(\text{mid position})\) ,分别表示已经求出右端点最靠右的子串的右端点和中心位置。如果目前要求的 \(i\) 节点没有超过 \(mr\),那么 \(p[i]\) 就可以是 \(p[2*pos-i]\) (对称点),但要对 \(mr-i+1\) 取 \(\min\),因为在 \(mr\) 右边的字符还不知道。如果 \(i\) 节点超过了 \(mr\) ,那就先直接取 \(1\) 。接下来就暴力继续匹配,因为一次成功匹配必然会带动右端点的推动,所以复杂度是 \(O(n)\) 的,代码如下:

  1. void Manacher(char *str,int len)
  2. {
  3. int n=1;
  4. mnc[1]='#';
  5. FOR(i,1,len)mnc[++n]=str[i],mnc[++n]='#';
  6. int mr=0,pos;
  7. FOR(i,1,n)
  8. {
  9. if(i<=mr)p[i]=std::min(p[(pos<<1)-i],mr-i+1);
  10. else p[i]=1;
  11. while(i-p[i]>=1&&i+p[i]<=n&&mnc[i-p[i]]==mnc[i+p[i]])p[i]++;
  12. if(chk_max(mr,i+p[i]-1))pos=i;
  13. }
  14. }

\(\text{Manacher}\) 算法进行 p[i]++ 时可以得到所有本质不同的回文串(不保证无重复)。

注意到一个与已经找过的串本质不同的回文串,一定能通过一次推动右端点得到,并且一次推动右端点最多增加一个回文串(如果以这个位置为右端点存在多个回文串,那么不是最长的串肯定在之前算过),这也同时证明了一个串内本质不同的回文子串是 \(O(n)\) 级别的。

那么可以利用 \(\text{Manacher}\) 求出若干个回文串,这一定包含了所有本质不同的回文子串,我们只用对它进行去重即可,后缀数组可以很方便的实现它,对长度相同的串,按 \(rk\) 进行排序,比较 \(\text{lcp}\) 是否为串长即可。

代码

  1. #include<bits/stdc++.h>
  2. #define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
  3. #define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
  4. template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
  5. template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
  6. typedef long long ll;
  7. const int N=2e5+5;
  8. struct SparseTable
  9. {
  10. int st[N][19],bin[(1<<18)+5];
  11. void init(int *arr,int n)
  12. {
  13. bin[1]=0;FOR(i,2,1<<18)bin[i]=bin[i>>1]+1;
  14. FOR(i,1,n)st[i][0]=arr[i];
  15. FOR(j,1,18)FOR(i,1,n-(1<<j)+1)
  16. st[i][j]=std::min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
  17. }
  18. int query(int l,int r)
  19. {
  20. int k=bin[r-l+1];
  21. return std::min(st[l][k],st[r-(1<<k)+1][k]);
  22. }
  23. }SH;
  24. int sa[N],rk[N],H[N],tmp[3][N];
  25. char str[N];
  26. char mnc[N];int p[N];
  27. int n;
  28. struct node
  29. {
  30. int l,r;
  31. bool operator <(const node &_)const
  32. {
  33. if(r-l+1!=_.r-_.l+1)return r-l+1<_.r-_.l+1;
  34. else return rk[l]<rk[_.l];
  35. }
  36. }pld[N];int pc;
  37. void get_SA(char *s,int n,int m)
  38. {
  39. int *x=tmp[0],*y=tmp[1],*c=tmp[2];
  40. memset(tmp,0,sizeof(tmp));
  41. x[n+1]=y[n+1]=0;
  42. FOR(i,1,m)c[i]=0;
  43. FOR(i,1,n)c[x[i]=s[i]]++;
  44. FOR(i,2,m)c[i]+=c[i-1];
  45. DOR(i,n,1)sa[c[x[i]]--]=i;
  46. for(int k=1;k<=n;k<<=1)
  47. {
  48. int p=0;
  49. FOR(i,n-k+1,n)y[++p]=i;
  50. FOR(i,1,n)if(sa[i]>k)y[++p]=sa[i]-k;
  51. FOR(i,1,m)c[i]=0;
  52. FOR(i,1,n)c[x[y[i]]]++;
  53. FOR(i,2,m)c[i]+=c[i-1];
  54. DOR(i,n,1)sa[c[x[y[i]]]--]=y[i];
  55. std::swap(x,y);
  56. p=x[sa[1]]=1;
  57. FOR(i,2,n)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
  58. if(n==p)break;
  59. m=p;
  60. }
  61. FOR(i,1,n)rk[sa[i]]=i;
  62. int k=0;
  63. FOR(i,1,n)
  64. {
  65. if(k)k--;
  66. if(rk[i]==1)continue;
  67. int j=sa[rk[i]-1];
  68. while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
  69. H[rk[i]]=k;
  70. }
  71. SH.init(H,n);
  72. }
  73. int get_lcp(int x,int y)
  74. {
  75. if(x==y)return n-x+1;
  76. x=rk[x],y=rk[y];
  77. if(x>y)std::swap(x,y);
  78. return SH.query(x+1,y);
  79. }
  80. void Manacher(char *str,int len)
  81. {
  82. int n=1;
  83. mnc[1]='#';
  84. FOR(i,1,len)mnc[++n]=str[i],mnc[++n]='#';
  85. pc=0;
  86. int mr=0,pos;
  87. FOR(i,1,n)
  88. {
  89. if(i<=mr)p[i]=std::min(p[(pos<<1)-i],mr-i+1);
  90. else p[i]=1;
  91. while(i-p[i]>=1&&i+p[i]<=n&&mnc[i-p[i]]==mnc[i+p[i]])
  92. {
  93. p[i]++;
  94. if(mnc[i-p[i]+1]=='#')
  95. pld[++pc]=(node){(i-p[i]+1+1)/2,(i+p[i]-1-1)/2};
  96. }
  97. if(chk_max(mr,i+p[i]-1))pos=i;
  98. }
  99. }
  100. int main()
  101. {
  102. int T;
  103. scanf("%d",&T);
  104. FOR(Ti,1,T)
  105. {
  106. scanf("%s",str+1);
  107. n=strlen(str+1);
  108. get_SA(str,n,256);
  109. Manacher(str,n);
  110. std::sort(pld+1,pld+1+pc);
  111. int ans=1;
  112. FOR(i,2,pc)
  113. {
  114. if(pld[i].r-pld[i].l+1==pld[i-1].r-pld[i-1].l+1&&get_lcp(pld[i].l,pld[i-1].l)>=pld[i].r-pld[i].l+1)
  115. continue;
  116. else ans++;
  117. }
  118. printf("Case #%d: %d\n",Ti,ans);
  119. }
  120. return 0;
  121. }

HDU 3948 The Number of Palindromes(Manacher+后缀数组)的更多相关文章

  1. hdu 3948 The Number of Palindromes

    The Number of Palindromes Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 262144/262144 K (J ...

  2. 【HDU3948】 The Number of Palindromes (后缀数组+RMQ)

    The Number of Palindromes Problem Description Now, you are given a string S. We want to know how man ...

  3. UVA 11475 Extend to Palindrome (kmp || manacher || 后缀数组)

    题目链接:点击打开链接 题意:给你一个串,让你在串后面添加尽可能少的字符使得这个串变成回文串. 思路:这题可以kmp,manacher,后缀数组三种方法都可以做,kmp和manacher效率较高,时间 ...

  4. 【HDOJ】3948 The Number of Palindromes

    后缀数组求不重复回文子串数目.注意dp数组. /* 3948 */ #include <iostream> #include <sstream> #include <st ...

  5. 【HDU 5030】Rabbit's String (二分+后缀数组)

    Rabbit's String Problem Description Long long ago, there lived a lot of rabbits in the forest. One d ...

  6. hdu 4691 最长的共同前缀 后缀数组 +lcp+rmq

    http://acm.hdu.edu.cn/showproblem.php? pid=4691 去年夏天,更多的学校的种族称号.当时,没有后缀数组 今天将是,事实上,自己的后缀阵列组合rmq或到,但是 ...

  7. HDU 5442 Favorite Donut(暴力 or 后缀数组 or 最大表示法)

    http://acm.hdu.edu.cn/showproblem.php?pid=5442 题意:给出一串字符串,它是循环的,现在要选定一个起点,使得该字符串字典序最大(顺时针和逆时针均可),如果有 ...

  8. [APIO2014]回文串 manacher 后缀数组

    题面:洛谷 题解: 还是这个性质:对于每个串而言,本质不同的回文串最多只有O(n)个. 所以我们先求出这O(n)个本质不同的回文串,然后对整个串求一次sa. 然后对于每个回文串,求出它的出现次数,更新 ...

  9. 【题解】回文串 APIO 2014 BZOJ 3676 COGS 1985 Manacher+后缀数组+二分

    这题可以用回文自动机来做,但是我并没有学,于是用Manacher+SA的做法O(nlogn)水过 首先,看到回文串就能想到用Manacher 同样还是要利用Manacher能不重复不遗漏地枚举每个回文 ...

随机推荐

  1. TestNG Suite 运行出现中文乱码如何解决

    场景: 用TestNG框架运行测试类,控制台视图输出出现中文乱码. 解决方案: 1.eclipse属性>workspace>other>utf-8 2.修改eclipse.ini 文 ...

  2. ES6 Symbol数据类型和set-map 数据结构

    Symbol数据类型 ES6新加的数据类型,提供一个独一无二的值 { let a1 = Symbol() ;let a2 = Symbol() } //声明 { let a3 = Symbol.for ...

  3. MVC Views文件夹下js无法访问问题解决方案

    出现这个问题是因为webconfig做的限制,可修改相应Views下的webconfig文件来解决. <system.webServer> <handlers> <rem ...

  4. jQuery-AutoComplete自动提示简单实现

    注:本次案列实现功能为 用户注册信息,如果数据库对应表中存在部分信息,点击已有的用户的用户名,自动补全其它已有的基本信息 实现思路:通过AutoComplete提示,异步通过用户名查询全表,充当Aut ...

  5. tp5命令行

    手册->命令行->自定义命令行 1.第一步,配置command.php文件 2.第二步,建立命令类文件 注意:该文件中代码,从文档中粘,以防写错. 名字啥的都不用改,就改命名空间 和 定义 ...

  6. python迭代-如何使用生成器函数实现可迭代对象

    如何使用生成器函数实现可迭代对象 问题举例: 实现一个可迭代对象的类,它能迭代出给定范围内 的所有素数: pn = PrimeNumbers(1, 30) for x in pn: print(x) ...

  7. git删除和提交

    //删除git分支git branch -D BranchNamegit branch -r -D origin/BranchNamegit push origin -d BranchName//提交 ...

  8. 【备忘】mybatis的条件判断用<choose>

    mybatis并没有if..else,在mybatis的sql mapper文件中,条件判断要用choose..when..otherwise.   <choose> <when t ...

  9. The world is in my hands

    Null项 其实我还是比较希望你能理解我的心情 无聊666回味

  10. EntityFramework如何创建索引?

    一.首先创建一个类 FwEntityTypeConfiguration 继承 EntityTypeConfiguration ,该类完整代码如下: using System.Data.Entity.M ...