识别子串 (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 的每一位的最短识别子串的长度。

【输入格式】

一行,一个由小写字母组成的字符串 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

【数据范围与提示】

对于 20% 的数据 L<=200

对于 40% 的数据 L<=5000

对于 60% 的数据 L<=20000

对于 100% 的数据 L<=100000

题解

可以发现,对于每一个唯一识别的子串,都可以更新一段答案

可以发现,在后缀自动机上的每一个叶子节点 $ x $,都是一段唯一识别的子串

考虑更新,记 $ l=len(x)-len(fa(x)),r=len(x) $,那么对于 $ [1,l-1] $ 的贡献为 $ r-i+1 $,对于 $ [l,r] $ 的贡献为 $ r-l+1 $

可以把 $r-i+1$ 的 $ i $ 提出,转化为 $query(i)-i$,开两棵线段树分别统计即可

代码

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define _(d) while(d(isdigit(ch=getchar())))
 4 using namespace std;
 5 int R(){
 6     int x;bool f=1;char ch;_(!)if(ch==‘-‘)f=0;x=ch^48;
 7     _()x=(x<<3)+(x<<1)+(ch^48);return f?x:-x;}
 8 const int N=1e6+5;
 9 int n,las=1,cnt=1,Rt=1;
10 char s[N];bool f[N];
11 struct node{int ch[30],fa,len;}tr[N];
12 void extend(int c){
13     int p=las,np=las=++cnt,q,nq;
14     tr[np].len=tr[p].len+1;
15     while(!tr[p].ch[c]&&p)
16         tr[p].ch[c]=np,p=tr[p].fa;
17     if(!p)return void(tr[np].fa=Rt);
18     if(tr[p].len+1==tr[q=tr[p].ch[c]].len)return void(tr[np].fa=q);
19     tr[nq=++cnt].len=tr[p].len+1;
20     memcpy(tr[nq].ch,tr[q].ch,sizeof tr[q].ch);
21     tr[nq].fa=tr[q].fa,tr[np].fa=tr[q].fa=nq;
22     while(p&&tr[p].ch[c]==q)
23         tr[p].ch[c]=nq,p=tr[p].fa;
24     return;
25 }
26 class seg{
27 private:
28     #define Ls rt<<1
29     #define Rs rt<<1|1
30 public:
31     int tr[N];
32     seg(){memset(tr,0x3f,sizeof tr);}
33     void update(int rt,int l,int r,int ql,int qr,int v){
34         if(ql>qr)return;
35         if(ql<=l&&qr>=r)return void(tr[rt]=min(tr[rt],v));
36         int mid=l+r>>1;
37         if(ql<=mid)update(Ls,l,mid,ql,qr,v);
38         if(qr>mid)update(Rs,mid+1,r,ql,qr,v);
39         return;
40     }
41     int query(int rt,int l,int r,int k){
42         if(l==r)return tr[rt];
43         int mid=l+r>>1;
44         if(k<=mid)return min(tr[rt],query(Ls,l,mid,k));
45         else return min(tr[rt],query(Rs,mid+1,r,k));
46     }
47 }T1,T2;
48 int main(){
49     scanf("%s",s+1),n=strlen(s+1);
50     for(int i=1;i<=n;i++)extend(s[i]-‘a‘);
51     for(int i=1;i<=cnt;i++)f[tr[i].fa]=1;
52     for(int i=1;i<=cnt;i++)
53         if(!f[i]){
54             int x=tr[i].len-tr[tr[i].fa].len,y=tr[i].len;
55             T1.update(1,1,n,1,x-1,y+1),T2.update(1,1,n,x,y,tr[tr[i].fa].len+1);
56         }
57     for(int i=1;i<=n;i++)
58         printf("%d\n",min(T1.query(1,1,n,i)-i,T2.query(1,1,n,i)));
59     return 0;
60 }

原文地址:https://www.cnblogs.com/chmwt/p/10661953.html

时间: 2024-10-12 01:21:38

识别子串 (string)——后缀自动机+线段树的相关文章

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树的每个节点子树中叶子的数量就是该节点

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

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 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

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的

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一遍得到答案.

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

Luogu 给你一个串 $ S $ 以及一个字符串数组 $ T_1 ~ T_m $ , $ q $ 次询问,每次问 $ S $ 的子串S[p_l,p_r]在 $ T_l ~ T_r $ 中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 题解时间 SAM的毒瘤题,无论是倍增来满足长度限制,线段树合并来求区间询问,应有尽有... 对于 $ T $ 串建广义SAM,之后考虑如何使得 $ S $ 在SAM上匹配时求出 $ S $ 在每个 $ T $ 的出现次数. 很明显用线段树