字符串初步

https://zybuluo.com/ysner/note/1222586

前置声明

  • 内容空着的板块,和标\(?????????\)的地方,笔者会慢慢地补全。
  • \(Bug\):最小循环表示法、\(kmp\)\(exkmp\)\(AC\)自动机

    前缀表达式和后缀表达式

    好像只会考中缀转后缀吧。
    总体来说,维护一个符号栈和一个结果栈,如果新符号的级别比栈顶的高,就入栈;否则就弹到栈顶符号等级小于等于栈顶,每弹出一个符号就塞进结果串。

算中缀表达式的值就是先转后缀,然后每遇到一个符号,就用它算掉前两个数。模拟一下应该不成问题。
复杂度\(O(2n)\)
符号等级\(\{)\}<\{+\}=\{-\}<\{*\}=\{/\}<\{(\}\)

#include<cstdio>
#include<cstring>
using namespace std;
char a[1000001];
char sym[1000001];
char num[1000001];
int p=-1,fu,i,tot;
int zhan[1000001];
bool order()//判断在一个符号放入时是否需要弹出 
{
    if (a[i]=='(') return 0;
    if (a[i]=='+'||a[i]=='-')
    {
        if (sym[fu]=='(') return 0;
        else return 1;
    }
    if (a[i]=='*'||a[i]=='/')
    {
        if (sym[fu]=='('||sym[fu]=='+'||sym[fu]=='-') return 0;
        else return 1;
    }
    if (a[i]=='^')
    {
        if (sym[fu]!='^') return 0;
        else return 1;
    }
}
int pooow(int a,int b)
{
    int sum=1;
    for (int k=1;k<=b;k++)
      sum*=a;
    return sum;
}
int main()
{
    gets(a);
    int len=strlen(a);
    for (i=0;i<len;i++)
    {
        if (a[i]>='0'&&a[i]<='9') tot++,num[++p]=a[i],num[++p]=' ';
        else
        {
            if (a[i]==')') 
            {
                while (sym[fu]!='(') num[++p]=sym[fu--],num[++p]=' ';
                fu--;
            }
            else
            {
                while (order()&&fu>0) num[++p]=sym[fu--],num[++p]=' ';
                sym[++fu]=a[i];
            }
        }
    }
    while (fu>0) num[++p]=sym[fu--];
    len=strlen(num);
    printf("%s\n",num);
    p=0;i=0;// p是栈顶指针 i是num数组指针 
    while (i<len&&tot>1)
    {
        for (;num[i]!='+'&&num[i]!='-'&&num[i]!='*'&&num[i]!='/'&&num[i]!='^';i++)
        {
            if (num[i]==' ') continue;
            zhan[++p]=num[i]-'0'; 
        }
        switch (num[i])
        {
            case '+':zhan[--p]+=zhan[p+1];break;
            case '-':zhan[--p]-=zhan[p+1];break;
            case '*':zhan[--p]*=zhan[p+1];break;
            case '/':zhan[--p]/=zhan[p+1];break;
            case '^':zhan[--p]=pooow(zhan[p],zhan[p+1]);break;
        }
        i+=2;
        for (int x=1;x<=p;x++)
          printf("%d ",zhan[x]);
        printf("%s\n",num+i);
        tot--;
    }
    return 0;
}

字符串的最小循环表示法

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

re int i=1,j=2;
while(j<=n)
{
  if(s[i]>s[j]) i=j,j=i+1;
  if(s[i]<s[j]) ++j;//除去不优的那个位置
  if(s[i]==s[j])
  {
    re int k=1;
    while(k<n) 
    {
      if(s[i+k]>s[j+k]) {i=j,j=i+1;break;}
      if(s[i+k]<s[j+k]) {++j;break;}
      ++k;
    }
  }
  return i;
}

但这样\(i\)移动太慢了。
\(s[i+k]>s[j+k]\)时,既然我们知道\(i~i+k-1\)\(j~j+k-1\)是一模一样的,为什么不能直接把\(i\)跳到\(i+k\)再比较呢?
这样,复杂度可以优化到\(O(n)\)

re int i=1,j=2;
while(j<=n)
{
  if(s[i]>s[j]) i=j,j=i+1;
  if(s[i]<s[j]) ++j;//除去不优的那个位置
  if(s[i]==s[j])
  {
    re int k=1;
    while(k<n) 
    {
      if(s[i+k]>s[j+k]) {i=i+k+1;if(i>=j) j=i+1;break;}
      if(s[i+k]<s[j+k]) {++j;break;}
      ++k;
    }
  }
  return i;
}

注意在\(i=i+k\)后可能导致\(i>=j\),需要把\(j=i+1\)

\(manacher\)算法

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

  • 需要分类讨论

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

  • 会出现很多子串被重复多次访问,时间效率大幅降低。

用一个辅助数组\(r\)表示每个点能够拓展出的回文串长度。
我们先设置一个辅助变量\(mr\),表示已经触及到的最右边的字符;一个辅助变量\(mid\),表示包含\(mr\)的回文串的对称轴所在的位置。
\(s[1]\)遍历到\(s[len]\),
\(mid<i<mr\)
\(i\)关于\(mid\)的对称点为\(j\),显然\(r[i]\)一定不会小于\(r[j]\)。(对称)
\(j\)可以通过\((mid<<1)−i\)算出。
那么我们就设置\(r[i]=r[j]\),(优化:\(r[i]\)的拓展就少走了\(r[j]\))然后接着尝试扩展,这样就可以较快地求出\(r[i]\),然后更新\(mr\)\(mid\)
\(i\)\(mr\)右边时,我们无法得知关于\(r[i]\)的信息,只好从\(1\)开始遍历,然后更新\(mr\)\(mid\)

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
char ch[22000005],ch2[22000005];
int r[22000005];
int main(){
    scanf("%s",ch2);
    memset(ch,0,sizeof(ch));
    int l=strlen(ch2);
    for(int i=l-1;i>=0;i--)
        ch[i*2+1]=ch2[i];
    l=2*l+1;
    int pos=0,mr=0;
    int ans=-1;
    for(int i=1;i<l;i++){
        r[i]=1;
        if(i<=mr)
            r[i]=min(r[pos*2-i],mr-i+1);
        for(int j=r[i];j<=l;j++){
            if(i-j<0||i+j>l) break;
            if(ch[i-j]!=ch[i+j]) break;
            r[i]=j+1;
        }
        if(i+r[i]-1>maxright) maxright=i+r[i]-1,pos=i;
        ans=max(ans,r[i]-1);
    }
    cout<<ans<<endl;
    return 0;
}

\(kmp\)算法

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

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

我们可以试一试。

  • \(S=\{abacbcd\}\)
    \(T=\{abad\}\)

如上,可以发现第\(4\)位失配。鉴于\(S[3]=S[1]=T[1]\),我们最好从\(T\)串第\(2\)位开始匹配。

  • \(S=\{abcabcd\}\)
    \(T=\{abcabb\}\)

如上,可以发现第\(6\)位失配。鉴于\(S[4]=T[4]=T[1],S[5]=T[5]=T[2]\),我们最好从\(T\)串第\(3\)位开始匹配。

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

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

求出\(next\)后,好好利用就可以了。剩下就是模拟。

re int i,j;
scanf("%s",s+1);
getchar();
scanf("%s",t+1);
n=strlen(s+1),m=strlen(t+1);
nex[1]=0;
for(i=2,j=0;i<=m;i++)
{
    while(j>0&&t[i]!=t[j+1]) j=nex[j];
    if(t[i]==t[j+1]) j++;
    nex[i]=j;
}
for(i=1,j=0;i<=n;i++)
{
    while(j>0&&t[j+1]!=s[i]) j=nex[j];
    if(t[j+1]==s[i]) j++;
    if(j==m)printf("%d\n",i-m+1),j=nex[m];
}

\(exKMP\)算法

\(Trie\)

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

  • 查找一字符串是否存在
  • 给所有字符串排序
  • 计算两字符串最长公共前缀(\(LCP\))长度
  • 与异或运算有关(详见某道求异或最大值的题

    \(AC\)自动机

    \(?????????\)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define il inline
#define re register
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
int n,cnt;
string s[N];
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
  if(ch=='-') t=-1,ch=getchar();
  while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
  return x*t;
}
struct Tree
{
  int fail,vis[26],end;
}AC[N];
struct res
{
  int num,pos;
  bool operator < (const res &x)
  {
    if(num==x.num) return pos<x.pos;
    return num>x.num;
  }
}Ans[N];
il void Upd(re int x)
{
  memset(AC[x].vis,0,sizeof(AC[x].vis));
  AC[x].fail=0;AC[x].end=0;
}
il void Build(re string s,re int num)
{
  re int l=s.length(),now=0;
  fp(i,0,l-1)
    {
      if(!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt,Upd(cnt);
      now=AC[now].vis[s[i]-'a'];
    }
  AC[now].end=num;
}
il void Get_fail()
{
  queue<int>Q;
  fp(i,0,25)
    if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]);
  while(!Q.empty())
    {
      re int u=Q.front();Q.pop();
      fp(i,0,25)
    if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]);
    else AC[u].vis[i]=AC[AC[u].fail].vis[i];
    }
}
il int Query(re string s)
{
  re int l=s.length(),now=0,ans=0;
  fp(i,0,l-1)
    {
      now=AC[now].vis[s[i]-'a'];
      for(re int t=now;t;t=AC[t].fail) ++Ans[AC[t].end].num;
    }
  return ans;
}
int main()
{
  ios::sync_with_stdio(false);
  while(1)
    {
      cin>>n;if(!n) break;
      cnt=0;Upd(0);
      fp(i,1,n)
    {
      cin>>s[i];Ans[i].num=0,Ans[i].pos=i;
      Build(s[i],i);
    }
      AC[0].fail=0;
      Get_fail();
      cin>>s[0];
      Query(s[0]);
      sort(&Ans[1],&Ans[n+1]);
      cout<<Ans[1].num<<endl;
      cout<<s[Ans[1].pos]<<endl;
      fp(i,2,n)
      if(Ans[i].num==Ans[i-1].num) cout<<s[Ans[i].pos]<<endl;
      else break;
    }
}

字符串哈希

详见专项总结从map到hash

猜你喜欢

转载自www.cnblogs.com/yanshannan/p/9357795.html
今日推荐