很容易想到只考虑后缀长度必须为\(max(height[rk[i]],height[rk[i]+1])+1\)(即\([i,i+x-1]\)代表的串只出现过一次)然后我正着做一遍反着做一遍,再取一个\(min\)最后挂了。。。

设\(x=max(height[rk[i]],height[rk[i]+1])+1\)我们考虑\(i\)的贡献,会给区间\([i,i+x-1]\)一个贡献x

,设\(r=i+x-1\)然后会给r+1一个贡献x+1就是(r+1)-i+1,接着是r+2的贡献(r+2)-i+1。。。

最后我们对每一个点求出这个点的最小的贡献。这堆东西可以用线段树维护。

值得注意的一点是当i+x-1>n时并不能产生贡献,因为此时已经到了字符串末尾。

我们无法加上\(max(height[rk[i]],height[rk[i]+1])+1\)最后的那个1。

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
#define mid ((l+r)>>1)
#define ls now<<1
#define rs now<<1|1
const int N=501000;
int c[N],x[N],y[N],sa[N],rk[N],height[N],n,m;
int lazy1[N*5],lazy2[N*5],mn[N*5];
char s[N];
void get_sa(){
for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++)y[++num]=i;
for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=1;i<=m;i++)c[i]+=c[i-1];
for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
for(int i=1;i<=n;i++)swap(x[i],y[i]);
x[sa[1]]=1;num=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
if(n==num)break;
m=num;
}
}
void get_height(){
int k=0;
for(int i=1;i<=n;i++)rk[sa[i]]=i;
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++;
height[rk[i]]=k;
}
}
void build(int l,int r,int now){
if(l==r){mn[now]=n;return;}
build(l,mid,ls);
build(mid+1,r,rs);
}
void update(int now){
mn[now]=min(mn[ls],mn[rs]);
}
void pushdown(int l,int r,int now){
if(l==r)return;
if(lazy1[now]){
mn[ls]=min(mn[ls],lazy1[now]);
mn[rs]=min(mn[rs],lazy1[now]);
if(lazy1[rs])lazy1[rs]=min(lazy1[rs],lazy1[now]);
else lazy1[rs]=lazy1[now];
if(lazy1[ls])lazy1[ls]=min(lazy1[ls],lazy1[now]);
else lazy1[ls]=lazy1[now];
lazy1[now]=0;
}
if(lazy2[now]){
mn[ls]=min(mn[ls],l+lazy2[now]);
mn[rs]=min(mn[rs],mid+1+lazy2[now]);
if(lazy2[rs])lazy2[rs]=min(lazy2[rs],lazy2[now]);
else lazy2[rs]=lazy2[now];
if(lazy2[ls])lazy2[ls]=min(lazy2[ls],lazy2[now]);
else lazy2[ls]=lazy2[now];
lazy2[now]=0;
}
}
void add1(int l,int r,int L,int R,int w,int now){
pushdown(l,r,now);
if(l==L&&r==R){
lazy1[now]=w;
mn[now]=min(mn[now],w);
return;
}
if(L>mid)add1(mid+1,r,L,R,w,rs);
else if(R<=mid)add1(l,mid,L,R,w,ls);
else add1(l,mid,L,mid,w,ls),add1(mid+1,r,mid+1,R,w,rs);
update(now);
}
void add2(int l,int r,int L,int R,int w,int now){
if(L>R)return;
pushdown(l,r,now);
if(l==L&&r==R){
lazy2[now]=w;
mn[now]=min(l+w,mn[now]);
return;
}
if(L>mid)add2(mid+1,r,L,R,w,rs);
else if(R<=mid)add2(l,mid,L,R,w,ls);
else add2(l,mid,L,mid,w,ls),add2(mid+1,r,mid+1,R,w,rs);
update(now);
}
int check(int l,int r,int x,int now){
pushdown(l,r,now);
if(l==r)return mn[now];
if(x>mid)return check(mid+1,r,x,rs);
else return check(l,mid,x,ls);
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
m=122;
get_sa();get_height();
build(1,n,1);
for(int i=1;i<=n;i++){
int tmp=max(height[rk[i]],height[rk[i]+1])+1;
if(i+tmp-1<=n)add1(1,n,i,i+tmp-1,tmp,1);
add2(1,n,i+tmp,n,-i+1,1);
}
for(int i=1;i<=n;i++)printf("%d\n",check(1,n,i,1));
return 0;
}

BZOJ 2865 字符串识别(后缀数组+线段树)的更多相关文章

  1. BZOJ 2865 字符串识别 | 后缀数组 线段树

    集训讲字符串的时候我唯一想出正解的题-- 链接 BZOJ 2865 题面 给出一个长度为n (n <= 5e5) 的字符串,对于每一位,求包含该位的.最短的.在原串中只出现过一次的子串. 题解 ...

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

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2865 唯一出现的子串就是每个后缀除去和别的后缀最长的 LCP 之外的前缀: 所以用这个更新一 ...

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

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2865 做出 ht[ ] 之后,sa[ ] 上每个位置和它前面与后面取 LCP ,其中较大的长 ...

  4. 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)

    点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...

  5. bzoj 1396: 识别子串 && bzoj 2865: 字符串识别【后缀数组+线段树】

    根据height数组的定义,和当前后缀串i最长的相同串的长度就是max(height[i],height[i+1]),这个后缀贡献的最短不同串长度就是len=max(height[i],height[ ...

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

    这道题各位大神好像都是用后缀自动机做的?.....蒟蒻就秀秀智商写一写后缀数组解法..... 求出Height数组后, 我们枚举每一位当做子串的开头. 如上图(x, y是height值), Heigh ...

  7. BZOJ.1396.识别子串(后缀自动机/后缀数组 线段树)

    题目链接 SAM:能成为识别子串的只有那些|right|=1的节点代表的串. 设这个节点对应原串的右端点为r[i],则如果|right[i]|=1,即\(s[\ [r_i-len_i+1,r_i-le ...

  8. BZOJ 5496: [2019省队联测]字符串问题 (后缀数组+主席树优化建图+拓扑排序)

    题意 略 分析 考场上写了暴力建图40分溜了-(结果只得了30分) 然后只要优化建边就行了 首先给出的支配关系无法优化,就直接A向它支配的B连边. 考虑B向以B作为前缀的所有A连边,做一遍后缀数组,两 ...

  9. 【XSY1551】往事 广义后缀数组 线段树合并

    题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\) ...

随机推荐

  1. GCC中的弱符号与强符号

    GCC中的弱符号与强符号 我们经常在编程中碰到一种情况叫符号重复定义.多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误.比如我们在目标文件A和目标文件B都 ...

  2. 【AnjularJS系列3 】 — 数据的双向绑定

    第三篇,双向的数据绑定 数据绑定是AnguarJS的特性之一,避免书写大量的初始代码从而节约开发时间 数据绑定指令提供了你的Model投射到view的方法.这些投射可以无缝的,毫不影响的应用到web应 ...

  3. 陆、jq基础语法

    一.概述:更加方便的处理html文档.events事件.动画效果和ajax交互等. 1.jq主要功能: (1)访问页面框架的局部. (2)修改页面表现 (3)更改页面的内容 (4)响应事件 (5)为页 ...

  4. echart的tooltip自定义换行

    自定义换行,内容很长的时候 tooltip : { trigger: 'axis', axisPointer : { // 坐标轴指示器,坐标轴触发有效 type : 'shadow' // 默认为直 ...

  5. 清空chrome浏览器缓存

    缓存是一个很深奥的东西,虽然查了半天,还是没有搞清楚,希望以后可以遇到前端大神,可以给一个傻瓜化的通俗易懂的解释 已经上线的,后续有迭代的软件,迭代的版本不应该手动清除缓存了,因为太麻烦,对客户来说不 ...

  6. nginx 过滤zip 类型的文件

    http://www.cnblogs.com/bass6/p/5500660.html

  7. 硬核官宣:台积电官宣6nm及7nm加强版工艺!

    台积电正式宣布了6nm(N6)工艺,在已有7nm(N7)工艺的基础上大幅度增强,号称可提供极具竞争力的高性价比,而且能加速产品研发.量产.上市速度. 这几年,曾经执行业牛耳的Intel在新工艺方面进展 ...

  8. phpexcel乱码问题

    php导出Excel乱码,只需在header函数前加入ob_end_clean();//清除缓冲区,避免乱码

  9. Java实现把两个数组合并为一个的方法总结

    本文实例讲述了Java实现把两个数组合并为一个的方法.分享给大家供大家参考,具体如下: 在Java中,如何把两个String[]合并为一个? 看起来是一个很简单的问题.但是如何才能把代码写得高效简洁, ...

  10. 洛谷 P1922 女仆咖啡厅桌游吧

    P1922 女仆咖啡厅桌游吧 题目背景 小v带萌萌的妹妹去玩,妹妹想去女仆咖啡馆,小v想去桌游吧. 妹妹:“我问你个问题,答不对你就做我一天的奴隶,答对了就今天我就全部听你的.” 小v:“全部都听!? ...