[关闭]
@ysner 2018-09-28T19:39:09.000000Z 字数 4634 阅读 2405

专题总结(字符串)

字符串


前置声明

前缀表达式和后缀表达式

对于直接求中缀表达式的值:

如果加上了未知数,就把数字栈变为多项式栈,用结构体储存所有项的系数,模拟一般多项式运算即可。

看一道考试题化简

字符串的最小循环表示法

该问题实质是求串的一个位置,从这个位置开始循环输出(输完),得到的字典序最小。
首先,这字符串在题目中有点像环一样被处理,可以复制一遍(破环为链)方便处理。
我们只能比较形成的字符串,于是我们要枚两个位置。
很容易想出一个暴力:(强制

  1. re int i=1,j=2;
  2. while(j<=n)
  3. {
  4. if(s[i]>s[j]) i=j,j=i+1;
  5. if(s[i]<s[j]) ++j;//除去不优的那个位置
  6. if(s[i]==s[j])
  7. {
  8. re int k=1;
  9. while(k<n)
  10. {
  11. if(s[i+k]>s[j+k]) {i=j,j=i+1;break;}
  12. if(s[i+k]<s[j+k]) {++j;break;}
  13. ++k;
  14. }
  15. }
  16. return i;
  17. }

但这样移动太慢了。
时,既然我们知道不优,为什么不能直接把跳到再比较呢?
这样,复杂度可以优化到

  1. il int work()
  2. {
  3. re int i=1,j=2,k=0;
  4. while(i<=n&&j<=n&&k<=n)
  5. {
  6. re int t=a[i+k]-a[j+k];
  7. if(!t) ++k;
  8. else
  9. {
  10. if(t>0) i+=k+1;
  11. if(t<0) j+=k+1;
  12. if(i==j) ++j;
  13. k=0;
  14. }
  15. }
  16. return min(i,j);
  17. }
  18. return i;
  19. }

注意在后可能导致,需要把

算法

该算法用于求字符串中最长回文串的长度。
解这个问题,最无脑的就是枚举两端点扫中间判,复杂度
稍微优化一些,就是枚举回文串正中间那个地方(要讨论是点还是点中间),然后同时向两边拓展,复杂度
但该“聪明的暴力”还是有很多不足:

算法一开始就在每两个字符中间及字符串两端插入另一字符'#'。
这样就不用讨论了,直接枚每个字符作为正中间即可。

用一个辅助数组表示每个点能够拓展出的回文串长度。
我们先设置一个辅助变量,表示已经触及到的最右边的字符;一个辅助变量,表示包含的回文串的对称轴所在的位置。
遍历到,

关于的对称点为,显然一定不会小于。(对称)
可以通过算出。
那么我们就设置,(优化:的拓展就少走了)然后接着尝试扩展,这样就可以较快地求出,然后更新
右边时,我们无法得知关于的信息,只好从开始遍历,然后更新

  1. int main()
  2. {
  3. scanf("%s",s+1);n=strlen(s+1);
  4. s[0]='*';s[n<<1|1]='#';
  5. fq(i,n,1) s[i<<1]=s[i],s[(i<<1)-1]='#';
  6. fp(i,1,n<<1)
  7. {
  8. p[i]=mx>i?min(p[2*id-i],mx-i):1;
  9. while(s[i-p[i]]==s[i+p[i]]) ++p[i];
  10. if(i+p[i]>mx) mx=i+p[i],id=i;
  11. ans=max(ans,p[i]-1);
  12. }
  13. printf("%d\n",ans);
  14. return 0;
  15. }

算法

算法主要是用来减少失配后,不利用已知信息而造成的 进行无意义匹配 的次数。
搞不清可以看看的动图。SYC博客

所以,整个的重点就在于当某一个字符与主串不匹配时,我们应该知道指针要移动到哪

我们可以试一试。

如上,可以发现第位失配。鉴于,我们最好从串第位开始匹配。

如上,可以发现第位失配。鉴于,我们最好从串第位开始匹配。

则可注意到,当匹配失败时,下一次最优开始匹配的位置 的前一个 ,存在着这样的性质:最前面的个字符和之前的最后个字符是一样的,即前面字符串的满足 前缀后缀相同 的长度
存这个值的数组被命名为数组。

然后怎么求这玩意儿?
方法是串自己匹配自己
我们从往后一一求出每个位置的

求出后,好好利用就可以了。剩下就是模拟。
复杂度

  1. int main()
  2. {
  3. scanf("%s%s",s+1,t+1);
  4. n=strlen(s+1);m=strlen(t+1);
  5. re int j=0;
  6. nxt[1]=0;
  7. fp(i,2,m)
  8. {
  9. while(j&&t[i]!=t[j+1]) j=nxt[j];
  10. if(t[i]==t[j+1]) ++j;
  11. nxt[i]=j;
  12. }
  13. j=0;
  14. fp(i,1,n)
  15. {
  16. while(j&&s[i]!=t[j+1]) j=nxt[j];
  17. if(s[i]==t[j+1]) ++j;
  18. if(j==m) {printf("%d\n",i-m+1);j=nxt[j];}
  19. }
  20. fp(i,1,m) printf("%d ",nxt[i]);puts("");
  21. return 0;
  22. }

算法

该算法用于求串与每一个后缀的最长公共前缀,是算法和算法相结合的产物。
继续使用算法中的变量,

看个图就知道这是什么玩意儿了。示意图
本质上是利用公共前缀减少存储空间。
用途:

给一个不用递归的模板

  1. il void Modify(re int x)
  2. {
  3. re int u=1;
  4. fq(d,30,0)
  5. {
  6. re int w=((x>>d)&1);
  7. if(!t[w][u]) t[w][u]=++tot;
  8. u=t[w][u];
  9. }
  10. }
  11. il int Query(re int x)
  12. {
  13. re int u=1,s=0;
  14. fq(d,30,0)
  15. {
  16. re int w=((x>>d)&1);
  17. if(t[!w][u]) s+=(1<<d),u=t[!w][u];else u=t[w][u];
  18. }
  19. return s;
  20. }

自动机

首先用所有的匹配串构建一颗树。
然后,像中的数组一样,为减少重复检查次数,建立失配指针。
构建原则是

这样(还是很像),每次失配后就跳转到失配指针,继续没配完部分的匹配。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<cstdlib>
  5. #include<cmath>
  6. #include<algorithm>
  7. #include<queue>
  8. #define ll long long
  9. #define il inline
  10. #define re register
  11. #define fp(i,a,b) for(re int i=a;i<=b;i++)
  12. #define fq(i,a,b) for(re int i=a;i>=b;i--)
  13. using namespace std;
  14. const int N=1e5+5;
  15. int n,cnt;
  16. string s[N];
  17. il int gi()
  18. {
  19. re int x=0,t=1;
  20. re char ch=getchar();
  21. while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  22. if(ch=='-') t=-1,ch=getchar();
  23. while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  24. return x*t;
  25. }
  26. struct Tree
  27. {
  28. int fail,vis[26],end;
  29. }AC[N];
  30. struct res
  31. {
  32. int num,pos;
  33. bool operator < (const res &x)
  34. {
  35. if(num==x.num) return pos<x.pos;
  36. return num>x.num;
  37. }
  38. }Ans[N];
  39. il void Upd(re int x)
  40. {
  41. memset(AC[x].vis,0,sizeof(AC[x].vis));
  42. AC[x].fail=0;AC[x].end=0;
  43. }
  44. il void Build(re string s,re int num)
  45. {
  46. re int l=s.length(),now=0;
  47. fp(i,0,l-1)
  48. {
  49. if(!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt,Upd(cnt);
  50. now=AC[now].vis[s[i]-'a'];
  51. }
  52. AC[now].end=num;
  53. }
  54. il void Get_fail()
  55. {
  56. queue<int>Q;
  57. fp(i,0,25)
  58. if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]);
  59. while(!Q.empty())
  60. {
  61. re int u=Q.front();Q.pop();
  62. fp(i,0,25)
  63. if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]);
  64. else AC[u].vis[i]=AC[AC[u].fail].vis[i];
  65. }
  66. }
  67. il int Query(re string s)
  68. {
  69. re int l=s.length(),now=0,ans=0;
  70. fp(i,0,l-1)
  71. {
  72. now=AC[now].vis[s[i]-'a'];
  73. for(re int t=now;t;t=AC[t].fail) ++Ans[AC[t].end].num;
  74. }
  75. return ans;
  76. }
  77. int main()
  78. {
  79. ios::sync_with_stdio(false);
  80. while(1)
  81. {
  82. cin>>n;if(!n) break;
  83. cnt=0;Upd(0);
  84. fp(i,1,n)
  85. {
  86. cin>>s[i];Ans[i].num=0,Ans[i].pos=i;
  87. Build(s[i],i);
  88. }
  89. AC[0].fail=0;
  90. Get_fail();
  91. cin>>s[0];
  92. Query(s[0]);
  93. sort(&Ans[1],&Ans[n+1]);
  94. cout<<Ans[1].num<<endl;
  95. cout<<s[Ans[1].pos]<<endl;
  96. fp(i,2,n)
  97. if(Ans[i].num==Ans[i-1].num) cout<<s[Ans[i].pos]<<endl;
  98. else break;
  99. }
  100. }

字符串哈希

详见专项总结从map到hash

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注