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 的初值是0,取答案时注意判掉0。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
using namespace std;
int const xn=5e5+5,xm=(xn<<1);
int n,m,rk[xn],sa[xn],tax[xn],tp[xn],ht[xn];
int cnt=1,ls[xm],rs[xm],len[xm],ps[xm];
char s[xn];
void Rsort()
{
  for(int i=1;i<=m;i++)tax[i]=0;
  for(int i=1;i<=n;i++)tax[rk[tp[i]]]++;
  for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
  for(int i=n;i;i--)sa[tax[rk[tp[i]]]--]=tp[i];
}
void work()
{
  for(int i=1;i<=n;i++)rk[i]=s[i],tp[i]=i;
  Rsort();
  for(int k=1;k<=n;k<<=1)
    {
      int num=0;
      for(int i=n-k+1;i<=n;i++)tp[++num]=i;
      for(int i=1;i<=n;i++)
    if(sa[i]>k)tp[++num]=sa[i]-k;
      Rsort(); memcpy(tp,rk,sizeof rk);
      rk[sa[1]]=1; num=1;
      for(int i=2;i<=n;i++)
    rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?num:++num;
      if(num==n)break;
      m=num;
    }
}
void get()
{
  int k=0;
  for(int i=1;i<=n;i++)
    {
      if(rk[i]==1)continue;
      if(k)k--; int j=sa[rk[i]-1];
      while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
      ht[rk[i]]=k;
    }
}
void build(int x,int l,int r)
{
  len[x]=n+1;
  if(l==r)return;
  ls[x]=++cnt; build(ls[x],l,mid);
  rs[x]=++cnt; build(rs[x],mid+1,r);
}
void update(int x,int l,int r,int L,int R,int v)
{
  if(l>=L&&r<=R){len[x]=min(len[x],v); return;}
  if(mid>=L)update(ls[x],l,mid,L,R,v);
  if(mid<R)update(rs[x],mid+1,r,L,R,v);
}
void chg(int x,int l,int r,int L,int R,int v)
{
  if(l>=L&&r<=R){ps[x]=max(ps[x],v); return;}
  if(mid>=L)chg(ls[x],l,mid,L,R,v);
  if(mid<R)chg(rs[x],mid+1,r,L,R,v);
}
int query(int x,int l,int r,int pos,int v)
{
  v=min(v,len[x]);
  if(l==r)return v;
  if(pos<=mid)return query(ls[x],l,mid,pos,v);
  else return query(rs[x],mid+1,r,pos,v);
}
int ask(int x,int l,int r,int pos,int v)
{
  v=max(v,ps[x]);
  if(l==r)return v;
  if(pos<=mid)return ask(ls[x],l,mid,pos,v);
  else return ask(rs[x],mid+1,r,pos,v);
}
int main()
{
  scanf("%s",s+1); n=strlen(s+1); m=125;
  work(); get(); build(1,1,n);
  for(int i=1;i<=n;i++)
    {
      int lcp=max(ht[i],ht[i+1]);
      if(sa[i]+lcp<=n)update(1,1,n,sa[i],sa[i]+lcp,lcp+1);//
      if(sa[i]+lcp+1<=n)chg(1,1,n,sa[i]+lcp+1,n,sa[i]);
    }
  for(int i=1;i<=n;i++)
    {
      int t=query(1,1,n,i,n+1),p=ask(1,1,n,i,0);
      if(p==0)printf("%d\n",t);//!
      else printf("%d\n",min(t,i-p+1));
    }
  return 0;
}

原文地址:https://www.cnblogs.com/Zinn/p/10088058.html

时间: 2024-10-09 06:54:47

bzoj 2865 字符串识别 —— 后缀数组的相关文章

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

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

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&

字符串(后缀数组||SAM):NOI2015 品酒大会

这道题可以转化为计数类问题. 若使用后缀数组,那么答案就是所有位置二元组(i,j)的lcp对0~lcp答案段的贡献.然后发现若一个二元组有x的贡献,那么对x-1有同样的贡献,考虑先求出lcp(max)的答案,再传给lcp(max-1)等等,复杂度是O(N)的. 若用SAM,那么需要求的答案在x与fa[x]的转移之间,因为后缀自动机中每个点所代表的出现位置和出现次数是相等的,可以合并,事实上还是用到了后缀数组的叠加的原理,从叶子节点一路传上去…………………………………………… 还有要注意INF要足

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

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3230 作出后缀数组,从 LCP 看每个位置对于本质不同子串的贡献,而且他们已经按前面部分排好序了,所以直接在 sa[ ] 上二分就能找到询问的子串. 找最长公共前缀就用 ht[ ] 和子串的长度比较就行.找最长公共后缀就一开始把原串翻转,做出翻转后的 ht[ ] ,就能查询了. 也可以二分一个最长公共后缀的位置,然后用正常的 ht[ ] 判断. 注意 long long . #includ

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)\)枚举显然是不可行的,应从 贡献 的角度取

字符串(后缀数组):HAOI2016 找相同子串

[HAOI2016]找相同子串 [题目描述] 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. [输入格式] 两行,两个字符串s1,s2,长度分别为n1,n2. [输出格式] 输出一个整数表示答案. [样例输入] aabb bbaa [样例输出] 10 [数据范围] 对于20%的数据,满足1≤n1,n2≤500: 对于40%的数据,满足1≤n1,n2≤5000: 对于100%的数据,满足1≤n1,n2≤200000,字符

使用后缀数组寻找最长公共子字符串JavaScript版

后缀数组很久很久以前就出现了,具体的概念读者自行搜索,小菜仅略知一二,不便讨论. 本文通过寻找两个字符串的最长公共子字符串,演示了后缀数组的经典应用. 首先需要说明,小菜实现的这个后缀数组算法,并非标准,只是借鉴了其中的思想. 小菜实现的算法,有两个版本,第一个是空间换时间,第二个是时间换空间. 空间换时间版本 1 /* 2 利用后缀数组获取两个字符串最长公共子字符串 3 空间换时间版本 4 @params 5 s1 String,要分析的字符串 6 s2 String,要分析的字符串 7 no

poj 3693 Maximum repetition substring(后缀数组)

题目链接:poj 3693 Maximum repetition substring 题目大意:求一个字符串中循环子串次数最多的子串. 解题思路:对字符串构建后缀数组,然后枚举循环长度,分区间确定.对于一个长度l,每次求出i和i+l的LCP,那么以i为起点,循环子串长度为l的子串的循环次数为LCP/l+1,然后再考虑一下从i-l+1~i之间有没有存在增长的可能性. #include <cstdio> #include <cstring> #include <vector>