[后缀自动机][分块]HackerRank Week of Code 30 .Substring Queries

题意是给出n个字符串,有Q组询问,每次询问第x和第y个字符串的最长公共子串

感觉这题会后缀自动机就挺水了…
因为字符串总长<=1e5,所以第一反应就可以分块
首先对每个串建后缀自动机

因为后缀自动机可以O(串长)求出两个串的最长公共子串,所以如果询问的两个串有一个串长小于sqrt(总串长),那么直接在较长串的后缀自动机上查询就可以了。

那如果有两个串都大于sqrt(总串长)呢
依然可以暴力查询,只不过记忆化一下,这样时间复杂度就对了…

因为最坏情况需要 numi=1numj=i+1min(ai,aj)
其中num为长度大于sqrt(总串长)的字符串个数,ai表示第i个字符串的长度

把字符串按串长排序
上面的式子推一下就变成 numi=1ai×(numi)
so复杂度就是 num×numi=1ai
因为num是长度大于sqrt(总长)的字符串的个数,所以是根号级别的,后面的sigma是O(n)级别的,所以这样子复杂度就 O(nn)

就是常数有点大

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <cmath>
#include <ctime>

using namespace std;

const int N=100010;

int n,tot,m,q;
int x[N],len[N],f[N],ib[N],rt[N];
char a[N*3];
vector<int> b,s;

int nxt[N*3][30],stp[N*3],fail[N*3],p[N],cnt;
int ans[320][320];

inline void fix(int &x,int y){
  if(x<y) x=y;
}

inline void extend(int rt,int &p,int x){
  int np=++cnt; stp[np]=stp[p]+1;
  while(!nxt[p][x]&&p) nxt[p][x]=np,p=fail[p];
  if(!p) fail[np]=rt;
  else{
    int q=nxt[p][x];
    if(stp[q]==stp[p]+1) fail[np]=q;
    else{
      int nq=++cnt; stp[nq]=stp[p]+1;
      fail[nq]=fail[q];
      memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
      fail[q]=fail[np]=nq;
      while(nxt[p][x]==q&&p) nxt[p][x]=nq,p=fail[p];
    }
  }
  p=np;
}

inline int calc(const int &u,const int &v){
  int cur=rt[v],ret=0,ans=0;
  for(int i=x[u];i<x[u]+len[u];i++){
    if(nxt[cur][a[i]-'a']){
      ret++; cur=nxt[cur][a[i]-'a'];
      fix(ans,ret);
      continue;
    }
    while(!nxt[cur][a[i]-'a']&&cur) cur=fail[cur];
    if(!cur) ret=0,cur=rt[v];
    else{
      ret=stp[cur]+1;
      fix(ans,ret);
      cur=nxt[cur][a[i]-'a'];
    }
  }
  return ans;
}

int main(){
  scanf("%d%d",&n,&q); int lst=0;
  for(int i=1;i<=n;i++){
    scanf("%s",a+lst+1);
    x[i]=lst+1; len[i]=strlen(a+lst+1);
    lst+=len[i]; tot+=len[i];
  }
  m=sqrt(tot);
  for(int i=1;i<=n;i++)
    (len[i]<=m?s:b).push_back(i);
  for(int i=1;i<=n;i++){
    rt[i]=p[i]=++cnt;
    for(int j=0;j<len[i];j++)
      extend(rt[i],p[i],a[x[i]+j]-'a');
  }
  for(int i=0;i<b.size();i++)
    f[b[i]]=i,ib[b[i]]=1;
  while(q--){
    int u,v; scanf("%d%d",&u,&v); u++; v++;
    if(ib[u]&&ib[v]){
      if(len[u]>len[v]) swap(u,v);
      if(!ans[f[u]][f[v]])
    ans[f[u]][f[v]]=ans[f[v]][f[u]]=calc(u,v);
      printf("%d\n",ans[f[u]][f[v]]); 
    }
    else{
      if(ib[u]) swap(u,v);
      int cur=rt[v],ret=0,ans=0;
      printf("%d\n",calc(u,v));
    }
  }
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值