[BZOJ1396]识别子串 后缀自动机+线段树

1396: 识别子串

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 451  Solved: 290
[Submit][Status][Discuss]

Description

Input

一行,一个由小写字母组成的字符串S,长度不超过10^5

Output

L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长.

Sample Input

agoodcookcooksgoodfood

Sample Output

1

2

3

3

2

2

3

3

2

2

3

3

2

1

2

3

3

2

1

2

3

4

HINT

Source

首先我们发现要找子串,想到用后缀自动机。

显然只出现一次的子串为right=1的节点(即结束节点只有一个)。对于这些节点,我们考虑对答案的贡献。

对于一个节点显然endpos-maxlen+1到endpos-minlen+1的子串是唯一的贡献为endpos-i+1。

对于endpos-minlen+1到endpos的节点,子串不唯一,我们需要将他向前延伸到endpos-minlen+1的点,贡献为minlen。

维护两棵线段树即可。

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdlib>
 4 #include<cstdio>
 5 #include<cmath>
 6 #include<algorithm>
 7 #define maxn 800005
 8 using namespace std;
 9 struct data {
10     int son[maxn][30],link[maxn],step[maxn],cnt,last;
11     int v[maxn],pos[maxn],size[maxn],endpos[maxn];
12     data() {last=cnt=1;}
13     void extend(int c,int x) {
14         int p=last,np=last=++cnt;
15         step[np]=step[p]+1;size[np]=1;endpos[np]=x;
16         while(p&&!son[p][c]) son[p][c]=np,p=link[p];
17         if(!p) link[np]=1;
18         else {
19             int q=son[p][c];
20             if(step[q]==step[p]+1) link[np]=q;
21             else {
22                 int nq=++cnt;
23                 memcpy(son[nq],son[q],sizeof(son[q]));
24                 link[nq]=link[q];
25                 link[q]=link[np]=nq;
26                 step[nq]=step[p]+1;
27                 while(son[p][c]==q&&p) son[p][c]=nq,p=link[p];
28             }
29         }
30     }
31     void pre() {
32         for(int i=1;i<=cnt;i++) v[step[i]]++;
33         for(int i=1;i<=cnt;i++) v[i]+=v[i-1];
34         for(int i=cnt;i;i--) pos[v[step[i]]--]=i;
35         for(int i=cnt;i;i--) {
36             int x=pos[i];
37             size[link[x]]+=size[x];
38             endpos[link[x]]=max(endpos[link[x]],endpos[x]);
39         }
40     }
41     struct tmp{
42         int minx[maxn],tag[maxn];
43         tmp(){memset(minx,97,sizeof(minx));for(int i=1;i<maxn;i++) tag[i]=2147483647;}
44         void pushup(int o) {
45             int ls=o<<1,rs=ls+1;
46             minx[o]=min(minx[ls],minx[rs]);
47         }
48         void pushdown(int o) {
49             int ls=o<<1,rs=ls+1;
50             minx[ls]=min(tag[o],minx[ls]);minx[rs]=min(minx[rs],tag[o]);
51             tag[ls]=min(tag[ls],tag[o]);tag[rs]=min(tag[rs],tag[o]);
52             tag[o]=2147483647;
53         }
54         void update(int l,int r,int o,int L,int R,int val) {
55             pushdown(o);
56             if(L<=l&&R>=r) {tag[o]=min(tag[o],val);minx[o]=min(minx[o],val);return;}
57             int mid=l+r>>1,ls=o<<1,rs=ls+1;
58             if(L<=mid)update(l,mid,ls,L,R,val);
59             if(R>mid) update(mid+1,r,rs,L,R,val);
60             pushup(o);
61         }
62         int query(int l,int r,int o,int x) {
63             pushdown(o);
64             if(l==r) return minx[o];
65             int mid=l+r>>1,ls=o<<1,rs=ls+1;
66             if(x<=mid) return query(l,mid,ls,x);
67             if(x>mid) return query(mid+1,r,rs,x);
68         }
69     }t1,t2;
70     void work(int x) {
71         for(int i=1;i<=cnt;i++) {
72             if(size[i]!=1) continue;
73             int maxlen=step[i],minlen=step[link[i]]+1;
74             t1.update(1,x,1,endpos[i]-maxlen+1,endpos[i]-minlen+1,endpos[i]+1);
75             t2.update(1,x,1,endpos[i]-minlen+1,endpos[i],minlen);
76         }
77         for(int i=1;i<=x;i++) {
78             int a1=t1.query(1,x,1,i)-i,a2=t2.query(1,x,1,i);
79             printf("%d\n",min(a1,a2));
80         }
81     }
82 }sam;
83 char ch[maxn];
84 int main() {
85     scanf("%s",ch+1);
86     int len=strlen(ch+1);
87     for(int i=1;i<=len;i++) sam.extend(ch[i]-‘a‘,i);
88     sam.pre();
89     sam.work(len);
90 } 

原文地址:https://www.cnblogs.com/wls001/p/8253481.html

时间: 2024-08-23 15:58:00

[BZOJ1396]识别子串 后缀自动机+线段树的相关文章

BZOJ 1396&amp;&amp;2865 识别子串[后缀自动机 线段树]

Description 在这个问题中,给定一个字符串S,与一个整数K,定义S的子串T=S(i, j)是关于第K位的识别子串,满足以下两个条件: 1.i≤K≤j. 2.子串T只在S中出现过一次. 例如,S="banana",K=5,则关于第K位的识别子串有"nana","anan","anana","nan","banan"和"banana". 现在,给定S,求对于S的

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中

BZOJ1396&amp;2865 识别子串 【后缀自动机 + 线段树】

题目 输入格式 一行,一个由小写字母组成的字符串S,长度不超过10^5 输出格式 L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 输入样例 agoodcookcooksgoodfood 输出样例 1 2 3 3 2 2 3 3 2 2 3 3 2 1 2 3 3 2 1 2 3 4 题解 BZOJ AC200纪念,, 这两题题干是一样的,但唯一不同的是..后者卡空间[MLE得飞起] 先说解法: 我们知道后缀自动机上的parent树的每个节点子树中叶子的数量就是该节点

识别子串 (string)——后缀自动机+线段树

题目 [题目描述] 一般地,对于一个字符串 S,和 S 中第 $ i $ 个字符 x,定义子串 $ T=S(i.j) $ 为一个关于 x 的识别子申,当且仅当: 1.$ i \leq x \leq j $ 2.T 在 S 巾只出现一次 比如,对于 banana 的第 $ 5 $ 个字符,“nana”, “anan”,“anana”, “nan”,“banan” 和“banana”都是关于它的识别子串. 说你写一个程序,计算出对对于一个字符串 S,关于 S 的每一位的最短识别子串的长度. [输入格

CF666E Forensic Examination(后缀自动机+线段树合并)

给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串数组建出后缀自动机,然后我们可以通过跳trans边找到S前i个字符代表的前缀的最长后缀.我们要找的是S[pl..pr]并不是以pr结束最长的后缀,但我们可以确定S[pl..pr]一定是当前点的祖先所以当我们跳到pr代表的点时我们倍增往上跳知道找到一个点的长度刚好大于等于pr-pl+1,这个点就是询问

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

CF700E:Cool Slogans(后缀自动机,线段树合并)

Description 给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1.问你给定字符串的最大价值子串的价值. Input 第一行读入字符串长度$n$,第二行是字符串. Output 一行答案. Sample Input1 3abc Sample Output1 1 Sample Input2 5ddddd Sample Output2 5 Sample Input3 11abracadabra Sample Output3 3 Solution 首先把后缀

Cool Slogans(后缀自动机+线段树+dp)

传送门:https://www.luogu.org/problemnew/show/CF700 先手动模拟一下: 原串:abracadabra s数组依次是:abracadabra,abra,a 可以发现,每一步我们找最长的在上一个串中出现两次的子串,即可得到最优解 很容易想到dp: 定义两个数组: dp[i]:使用节点i最长的那个字符串的答案mx[i]:节点i最长的那个字符串对应的节点 设A是B的子串 if(A在B中出现两次) dp[B]=dp[A]+1,mx[B]=B;else dp[B]=

CF.666E.Forensic Examination(广义后缀自动机 线段树合并)

题目链接 \(Description\) 给定串S和m个串Ti.Q次询问,每次询问l,r,pl,pr,求S[pl~pr]在Tl~Tr中的哪个串出现次数最多,输出最多次数及其T的下标.若有多个,输出下标最小的. \(Solution\) 挺好的题吧 对T个串建SAM,然后要求出SAM每个节点上|right|最大的是哪个串. 每个节点的|right|可以在DFS parent树时合并子节点得到,如果用线段树维护,|right|最大的位置也可以合并得到. 这样可以离线处理询问,最后DFS一遍得到答案.