zoukankan      html  css  js  c++  java
  • 【LuoguP7114】[NOIP2020] 字符串匹配 (扩展KMP算法)

    题目描述

    传送门

    Sol

    看到是一个与周期串有关的问题,朴素的KMP算法求出来的boder只能帮助我们求出一个周期串的最小循环节,这在本题中是不够的。因为我们要知道对于一个 AB 来说它最多能往后循环多少次。
    虽然似乎可以用二分来解决。

    考虑 扩展KMP算法
    用于在线性时间内求解一个串的所有后缀与另一个串的 LCP 长度。

    暴力算法显然是 (O(n^2))(,)和其他字符串算法类似(,)这里也是通过充分利用已经匹配完得到的信息来将整个算法的复杂度降低至线性。
    我们记(Z[i]) 表示后缀 (s[i...n])(s)(LCP) 长度。
    从前往后计算每一个 (Z)。考虑记录下 ((p+Z[p]-1)) 最大的 (p)(,)即匹配过的字符的最右端。
    (p+Z[p]-1=R) 那么对于当前要求解的 (Z[i]) 来说(,)如果 (R<i) 我们直接暴力求解即可。
    考虑 (Rgeq i)的情况(,)发现 (s[i...R]) 这一段能否和 (s[1...(R-i+1)]) 匹配上可以直接由 (Z[i-p+1]) 来得知(画个图就很好理解了)。
    求完一个 (Z[i]) 后更新一下 (p)(R) 即可。(注意 (Z[1]=n) 要特殊处理)
    这样我们发现对于每一个字符最多只会被匹配一次(,)复杂度就是(O(n))的。
    核心代码:

    char S[N];
    int Z[N],n,P,R;
    inline void Calc_Z(){
      Z[1]=n;P=0,R=0;
      for(int i=2;i<=n;++i) {
        int j=0;
        if(R<i) {
          for(j=0;i+j<=n;++j) if(S[i+j]!=S[j+1]) break;
          Z[i]=j;
        }
        else Z[i]=min(R-i+1,Z[i-P+1]);
        if(i+Z[i]-1>=R) {
          for(j=Z[i];i+j<=n;++j) if(S[i+j]!=S[j+1]) break;
          Z[i]=j;
        }
        if(i+Z[i]-1>R) P=i,R=i+Z[i]-1;
      }
      return;
    }
    

    对于本题来说,我们枚举AB是什么,这样也能知道 C 的可能的情况。
    假设枚举的 AB 是前缀 (i) ,那么 C 只可能是后缀 (i+1) ,以及后缀 (i+1) 去掉前面的若干 AB 后得到的后缀。
    我们预处理出对于每一个前缀和后缀的出现奇数次字符的个数。
    发现对于AB循环奇数次和偶数次来说,它们的 C 的上述值分别相同。
    于是分AB循环奇数次和偶数次来分别考虑合法的A即可。
    从前往后做的过程中我们记录 (tr[i]) 表示当前有多少个前缀的出现奇数次字符个数为 (i)
    容易发现询问的时候就是一个前缀和,直接暴力询问总的复杂度就是 (O(26n)),我们强行来个树状数组就是 (O(nlog26)) 差不多就是线性的。

    code:

    #include<bits/stdc++.h>
    using namespace std;
    #define Set(a,b) memset(a,b,sizeof(a))
    template<class T>inline void init(T&x) {
      x=0;char ch=getchar();bool t=0;
      for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
      for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
      if(t) x=-x;return;
    }
    typedef long long ll;
    const int N=2e6+10;
    char S[N];
    int Z[N],P,R,n;
    int Lnum[N],Rnum[N];
    void Calc_Z(){
      P=0,R=0;Z[1]=n;
      for(int i=2;i<=n;++i) {
        int j=0;
        for(j=max(0,min(R-i+1,Z[i-P+1]));i+j<=n;++j) if(S[i+j]!=S[j+1]) break;
        Z[i]=j;if(i+Z[i]-1>=R) P=i,R=i+Z[i]-1;
      }
      return;
    }
    class Bac{
      int num[26];
      int oddnum;
    public:
      Bac(){Set(num,0);oddnum=0;}
      void clear(){Set(num,0);oddnum=0;}
      int GT(){return oddnum;}
      void Insert(char c){
        num[c-'a']^=1;
        if(num[c-'a']) ++oddnum;else --oddnum;
      }
    }B;
    int tr[28];
    #define lowbit(a) ((a)&(-a))
    inline void Inc(int p,int x){for(;p<=27;p+=lowbit(p)) tr[p]+=x;return;}
    inline int  Qry(int p){int re=0;for(;p;p-=lowbit(p)) re+=tr[p];return re;}
    int main()
    {
      int T;
      init(T);
      while(T--) {
        scanf("%s",S+1);
        B.clear();n=strlen(S+1);
        for(int i=1;i<=n;++i) B.Insert(S[i]),Lnum[i]=B.GT();
        B.clear();
        for(int i=n;i;--i) B.Insert(S[i]),Rnum[i]=B.GT();
        Calc_Z();
        Set(tr,0);
        Inc(2,1);
        ll ans=0;
        for(int i=2;i<n;++i) {
          int L=Z[i+1];
          int km=min((n-1)/i,L/i+1);
          int num1=Qry(Rnum[i+1]+1),odn=(km+1)/2;
          int num2=(i<<1)>=n? 0:Qry(Rnum[(i<<1)+1]+1),evn=km/2;
          ans+=(ll)num1*odn+(ll)num2*evn;
          Inc(Lnum[i]+1,1);
        }
        printf("%lld
    ",ans);
      }
    }
    
    
  • 相关阅读:
    [Debug]驱动程序调测方法与技巧
    [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析
    ios多线程-GCD基本用法
    用PHP抓取页面并分析
    IOS开发-KVC
    IOS开发-KVO
    JavaScript垃圾回收(三)——内存泄露
    JavaScript垃圾回收(二)——垃圾回收算法
    JavaScript垃圾回收(一)——内存分配
    JavaScript闭包(二)——作用
  • 原文地址:https://www.cnblogs.com/NeosKnight/p/14317175.html
Copyright © 2011-2022 走看看