bzoj 3230 相似子串——后缀数组

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3230

作出后缀数组,从 LCP 看每个位置对于本质不同子串的贡献,而且他们已经按前面部分排好序了,所以直接在 sa[ ] 上二分就能找到询问的子串。

找最长公共前缀就用 ht[ ] 和子串的长度比较就行。找最长公共后缀就一开始把原串翻转,做出翻转后的 ht[ ] ,就能查询了。

也可以二分一个最长公共后缀的位置,然后用正常的 ht[ ] 判断。

注意 long long 。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5,K=20;
int n,sa[2][N],rk[2][N],tp[N],tx[N];ll sm[N];
int ht[2][N][K],bin[K],lg[N];
char s[N];
struct Node{int ps,len;};
int Mn(int a,int b){return a<b?a:b;}
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)fx=0;ch=getchar();}
  while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar();
  return fx?ret:-ret;
}
ll rdl()
{
  ll ret=0;bool fx=1;char ch=getchar();
  while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)fx=0;ch=getchar();}
  while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar();
  return fx?ret:-ret;
}
void init()
{
  lg[1]=0;for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
  bin[0]=1;for(int i=1;i<=lg[n];i++)bin[i]=bin[i-1]<<1;
}
void Rsort(int n,int nm,int x)
{
  for(int i=1;i<=nm;i++)tx[i]=0;
  for(int i=1;i<=n;i++)tx[rk[x][i]]++;
  for(int i=2;i<=nm;i++)tx[i]+=tx[i-1];
  for(int i=n;i;i--)sa[x][tx[rk[x][tp[i]]]--]=tp[i];
}
void get_sa(int n,int x)
{
  int nm=30;
  for(int i=1;i<=n;i++)tp[i]=i,rk[x][i]=s[i]-‘a‘+1;
  Rsort(n,nm,x);
  for(int k=1;k<=n;k<<=1)
    {
      int tot=0;
      for(int i=n-k+1;i<=n;i++)tp[++tot]=i;
      for(int i=1;i<=n;i++)
    if(sa[x][i]>k)tp[++tot]=sa[x][i]-k;
      Rsort(n,nm,x);memcpy(tp,rk[x],sizeof rk[x]);
      nm=1;rk[x][sa[x][1]]=1;
      for(int i=2,u,v;i<=n;i++)
    {
      u=sa[x][i]+k;v=sa[x][i-1]+k;if(u>n)u=0;if(v>n)v=0;
      rk[x][sa[x][i]]=(tp[sa[x][i]]==tp[sa[x][i-1]]&&tp[u]==tp[v])?nm:++nm;
    }
      if(nm==n)break;
    }
}
void get_ht(int n,int x)
{
  for(int i=1,k=0,j;i<=n;i++)
    {
      for(k?k--:0,j=sa[x][rk[x][i]-1];i+k<=n&&j+k<=n&&s[i+k]==s[j+k];k++);
      ht[x][rk[x][i]][0]=k;
    }
  for(int j=1;j<=lg[n];j++)
    for(int i=1;i+bin[j]-1<=n;i++)
      ht[x][i][j]=Mn(ht[x][i][j-1],ht[x][i+bin[j-1]][j-1]);
}
int get_lcp(int l,int r,int x)
{
  if(l==r)return n-l+1;
  l=rk[x][l]; r=rk[x][r]; if(l>r)swap(l,r);
  int d=lg[r-l];
  return Mn(ht[x][l+1][d],ht[x][r-bin[d]+1][d]);
}
Node fnd(ll x)
{
  int l=1,r=n,ret=0;
  while(l<=r)
    {
      int mid=l+r>>1;
      if(sm[mid]>=x)ret=mid,r=mid-1;
      else l=mid+1;
    }
  if(!ret)return (Node){0,0};
  int d=n-sa[0][ret]+1-ht[0][ret][0];
  d=ht[0][ret][0]+(x-sm[ret-1]);
  return (Node){sa[0][ret],d};
}
int main()
{
  n=rdn();int Q=rdn();scanf("%s",s+1);init();
  get_sa(n,0);get_ht(n,0);
  reverse(s+1,s+n+1);get_sa(n,1);get_ht(n,1);
  for(int i=1;i<=n;i++)
    sm[i]=sm[i-1]+(n-sa[0][i]+1)-ht[0][i][0];
  ll x,y; Node a,b;//long long!!!
  while(Q--)
    {
      x=rdl(); y=rdl();//long long!!
      a=fnd(x); b=fnd(y);
      if(!a.ps||!b.ps){puts("-1");continue;}
      x=Mn(get_lcp(a.ps,b.ps,0),Mn(a.len,b.len));
      y=Mn(get_lcp(n-(a.ps+a.len-1)+1,n-(b.ps+b.len-1)+1,1),Mn(a.len,b.len));
      printf("%lld\n",x*x+y*y);
    }
  return 0;
}

原文地址:https://www.cnblogs.com/Narh/p/10084051.html

时间: 2024-08-29 21:30:29

bzoj 3230 相似子串——后缀数组的相关文章

BZOJ 3230 相似子串 | 后缀数组 二分 ST表

BZOJ 3230 相似子串 题面 题解 首先我们要知道询问的两个子串的位置. 先正常跑一遍后缀数组并求出height数组. 对于每一个后缀suffix(i),考虑以i开头的子串有多少是之前没有出现过的,也就是考虑左端点在i.右端点在什么范围内时这个子串没有出现过--答案是右端点在[i + height[i] - 1, n]范围内时这个子串没出现过,即右端点在没有被"i与排在前一个的后缀的公共前缀"覆盖的部分时,这个子串没有出现过. 那么我们记录以每个i开头的新子串的数量,求前缀和,然

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heights数组中相邻的3个后缀, 假如我们枚举s2的第一个字符为开头, 那我们发现, 长度至少为len = max(x, y)+1, 才能满足题意(仅出现一次). 这个很好脑补...因为s2和其他串的LCP是RMQ, 肯定会<=LCP(s1,s2)或<=LCP(s2,s3). 然后就用len去更新s2中

poj 1743 最长不重叠重复子串 后缀数组+lcp+二分

题比较容易读懂,但是建模需动点脑子: 一个子串加常数形成的子串认为跟子串相同,求最长不重叠重复子串 题目中说 is disjoint from (i.e., non-overlapping with) at least one of its other appearance(s) 意味着不能重叠,举个例子 1, 2,3,  52, 53,54 1,2, 3和 52, 53,54满足题意,差值为51 枚举差值肯定不行------看了题解明白的:: 后项减去前一项得到: 1,1,1,49,1,1  

BZOJ 3230: 相似子串( RMQ + 后缀数组 + 二分 )

二分查找求出k大串, 然后正反做后缀数组, RMQ求LCP, 时间复杂度O(NlogN+logN) --------------------------------------------------------------------- #include<cstdio> #include<algorithm> #include<cstring> #include<cctype> using namespace std; typedef long long

URAL 1297. Palindrome(输出最长回文子串--后缀数组)

Input The input consists of a single line, which contains a string of Latin alphabet letters (no other characters will appear in the string). String length will not exceed 1000 characters. Output The longest substring with mentioned property. If ther

bzoj 3238: [Ahoi2013]差异 -- 后缀数组

3238: [Ahoi2013]差异 Time Limit: 20 Sec  Memory Limit: 512 MB Description Input 一行,一个字符串S Output 一行,一个整数,表示所求值 Sample Input cacao Sample Output 54 HINT 2<=N<=500000,S由小写英文字母组成 Source 后缀数组+单调栈水过... #include<map> #include<cmath> #include<

BZOJ 3172 Tjoi2013 单词 后缀数组

题目大意:给定一个n个单词的文章,求每个单词在文章中的出现次数 文章长度<=10^6(不是单词长度<=10^6,不然读入直接超时) 首先将所有单词用空格连接成一个字符串,记录每个单词的起始位置和长度 然后求后缀数组,对于每个单词后缀数组中一定有连续一段后缀以这个单词开头,我们通过一开始记录的起始位置找到这个单词的后缀,然后左右端点二分答案,满足左右端点之间的后缀与原单词的LCP都当与等于原单词长度即可 时间复杂度O(nlogn) #include<cstdio> #include&

bzoj 3238 [Ahoi2013]差异 后缀数组 + 单调栈

题目链接 Description 一个长度为\(n\)的字符串\(S\),令\(T_i\)表示它从第\(i\)个字符开始的后缀.求\[\sum_{1\leq i\leq j\leq n}len(T_i)+len(T_j)-2*lcp(T_i,T_j)\]其中,\(len(a)\)表示字符串\(a\)的长度,\(lcp(a,b)\)表示字符串\(a\)和字符串\(b\)的最长公共前缀. \(2\leq n\leq 500000\) 思路 \(O(n^2)\)枚举显然是不可行的,应从 贡献 的角度取

bzoj 2865 字符串识别 —— 后缀数组

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2865 唯一出现的子串就是每个后缀除去和别的后缀最长的 LCP 之外的前缀: 所以用这个更新一段区间的答案,可以用线段树维护: 在 sa[i] ~ sa[i]+LCP+1 位置的答案由 LCP+1 更新,sa[i]+LCP+1 之后的位置就更新一个位置 ps 表示从 ps 到本位置的子串也可以: 最后两个取min即可: 注意如果整个后缀就是 LCP,那么就不能更新了: 线段树中 ps 的初值