12.11闲话

发布时间 2023-12-11 20:36:27作者: Vsinger_洛天依

下雪了,明天不用跑操了,赢

但是别人去打雪仗我来机房写字符串了,输

听说7班有人打雪仗摔了一下给摔失忆了,这么抽象?

学校概率的自助居然有一道期望题,Orz

(就是类似今年S组初赛那个期望再简单亿点)

(大概就是有个转盘然后格子有那个价值,然后算概率再乘上价值再根据\(E(x)+E(y)=E(x+y)\)然后出来了)

人均切了所以九年级人均概率期望大神Orz

今天试图卡常战胜评测机波动造成的90ms字符串匹配但是失败了我的KMP最后也只达到了217ms,输输输


把昨天欠的Hash打完了

//因为我有define int long long的习惯所以下面的int都是ll
const int P1=1e9+7,P2=1e9+9;
int h1[N],h2[N],z1[N],z2[N];
inline void Init(const char *s){
    int len=strlen(s+1),b=131;
    z1[0]=z2[0]=1;
    for(int i=1;i<=len;i++){
        (z1[i]=z1[i-1]*b)%=P1;
        (z2[i]=z2[i-1]*b)%=P2;
        (h1[i]=h1[i-1]*b+(s[i]-'A'+1%P1))%=P1;
        (h2[i]=h2[i-1]*b+(s[i]-'A'+1%P2))%=P2;
    }
}
inline int Get1(int l,int r){
    return (h1[r]-h1[l-1]*z1[r-l+1]%P1+P1)%P1;
}
inline int Get2(int l,int r){
    return (h2[r]-h2[l-1]*z2[r-l+1]%P2+P2)%P2;
}

接着复习一下KMP因为快忘了

KMP

KMP用来解决的问题是模式串在文本串的匹配问题

暴力\(O(nm)\)明显不行,然后哈希的\(O(n+m)\)虽然没问题但是因为不稳定所以可能会白给

定义

  • \(Pre_i\)

    一个字符串长为 \(i\) 的前缀

  • Border

    一个字符串 \(s\)Border 为一个同时是 \(s\) 前后缀的真子串。

  • \(nxt_i\)

    当前字符串长 \(i\) 的前缀最长的 Border 的长度。

\(nxt\)数组

  • 暴力求

    枚举\(j\in[1,i-1]\)并检查\(A[i-j+1\sim i]\)是否等于\(A[1\sim j]\)

    这个算法对于每个\(i\)都枚举了\(i-1\)个非前缀子串

    时间复杂度\(O(n^2)\)

    然后发现太慢了所以考虑能不能更快

当然能了

  • 优化

    \(j_0\)\(nxt[i]\)的候选项,即满足$$j_0<i且A[i-j_0+1\sim i]=A[1\sim j_0]$$那么小于\(j_0\)的最大的满足以上条件的候选项是\(nxt[j_0]\)

    或者可以说\(nxt[j_0]+1\sim j_0-1\)之间所有数都不是\(nxt\)的候选项

    \(nxt[i-1]\)计算完毕之后可以得知\(nxt[i-1]\)的所有候选项为$$nxt[i-1],nxt[nxt[i-1]],nxt[nxt[nxt[i-1]]],...\ $$

    而如果一个\(j\)\(nxt[i]\)的候选项,那么\(j-1\)也必须是\(nxt[j-1]\)的候选项

    因此计算\(nxt[i]\)时只要把$$nxt[i-1],nxt[nxt[i-1]]+1,nxt[nxt[nxt[i-1]]]+1,...$$作为选项即可


    对于待匹配串\(abababaac\)

    模式串 前缀 匹配结果
    \(abababaac\) \(abababa\) 匹配失败\(nxt(7)=5\)
    \(abababaac\) \(ababa\) 匹配失败\(nxt(5)=3\)
    \(abababaac\) \(aba\) 匹配失败\(nxt(3)=1\))
    \(abababaac\) \(a\) 匹配成功\(nxt(1)=0\)

    那么\(nxt(8)\)也就等于\(1\)

    若在第\(i\)个匹配成功则这个字符串的的\(nxt(x)=nxt(i)+1\)

    时间复杂度\(O(n)\)

算法

  • 模式串是要查找的串。

  • 文本串是被查找的串。

KMP算法的思路其实和求 \(nxt\) 数组差不多,如果当前两个串失配了,那么在模式串中不断查找 \(nxt[nxt[nxt[\cdots]]]\),直到匹配成功继续向下

代码

直接拿的之前题里的

题面

\(n\)组询问每次询问一个字符串在另一个字符串里出现的次数

#include<bits/stdc++.h>
#define int long long
const int maxm = 0X66CCFF;
namespace IO{
    inline void close(){std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);}
    inline void Fire(){freopen(".in","r",stdin);freopen(".out","w",stdout);}
    inline int read(){int s = 0,w = 1;char ch = getchar();while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}while(ch>='0'&&ch<='9'){ s = s*10+ch-'0';ch = getchar();}return s*w;}
    inline void write(int x){char F[200];int tmp=x>0?x:-x,cnt=0;;if(x<0)putchar('-') ;while(tmp>0){F[cnt++]=tmp%10+'0';tmp/=10;}if(cnt==0)putchar('0');while(cnt>0)putchar(F[--cnt]);putchar(' ');}
}
using namespace IO;
int nxt[maxm],f[maxm],n,m,ans;
signed main(){
    n=read();
    while(n--){
        memset(nxt,0,sizeof(nxt));
        memset(f,0,sizeof(f));
        std::string a,b;
        std::cin>>a>>b;
        a=' '+a;b=' '+b;
        int N=a.size()-1,M=b.size()-1;
        nxt[1]=0;ans=0;
        for(int i=2,j=0;i<=N;i++){
            while(j>0 && a[i]!=a[j+1]) j=nxt[j];
            if(a[i]==a[j+1]) j++;
            nxt[i]=j;
        }
        for(int i=1,j=0;i<=M;i++){
            while(j>0 && (j==N || b[i]!=a[j+1])) j=nxt[j];
            if(b[i]==a[j+1]) j++;
            f[i]=j;
            if(f[i]==N) ans++;//此时是A在B中的某一次出现
        }
        write(ans);puts("");
    }
}
卡常用的抽象代码

题面

给定若干字符串(这些字符串总长\(<4\times 10^5\) ),在每个字符串中求出所有既是前缀又是后缀的子串长度。

#include<bits/stdc++.h>
#define maxm 400005
namespace IO{
    inline void close(){std::ios::sync_with_stdio(false);std::cin.tie(nullptr);}
    inline void Fire(){freopen(".in","r",stdin);freopen(".out","w",stdout);}
    inline int read(){int s = 0,w = 1;char ch = getchar();while(ch<'0'||ch>'9'){ if(ch == '-') w = -1;ch = getchar();}while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-'0';ch = getchar();}return s*w;}
    inline void write(int x){char F[200];int tmp=x>0?x:-x,cnt=0;;if(x<0)putchar('-') ;while(tmp>0){F[cnt++]=tmp%10+'0';tmp/=10;}if(cnt==0)putchar('0');while(cnt>0)putchar(F[--cnt]);putchar(' ');}
}
using namespace IO;
signed main(){
    register int nxt[maxm];
    register std::stack<int> sta;
    close();
    std::string a;
    while(std::cin>>a){
        register int N(a.size());
        for(register int i(2);i<=N;++i){
            int j(nxt[i-1]);
            while(j!=0 && a[j]!=a[i-1]) 
                j=nxt[j];
            if(a[j]==a[i-1])nxt[i]=j+1;
            else nxt[i]=0;
        }
        register int j(nxt[N]);
        while(j)
            sta.push(j),j=nxt[j];
        while(!sta.empty())
            write(sta.top()),sta.pop();
        write(N);
        puts("");
    }
}