字符串(后缀自动机):NOI 2016 优秀的拆分

【问题描述】

如果一个字符串可以被拆分为 AABB 的形式,其中 A 和 B 是任意非空字符串, 则我们称该字符串的这种拆分是优秀的。

例如,对于字符串 aabaabaa,如果令 A = aab, B = a, 我们就找到了这个字符串拆分成 AABB 的一种方式。

一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。 比如我们令 A = a, B = baa,也可以用 AABB 表示出上述字符串;但是,字符串abaabaa 就没有优秀的拆分。

现在给出一个长度为 n 的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。 这里的子串是指字符串中连续的一段。

以下事项需要注意:

1. 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被计入答案。

2. 在一个拆分中,允许出现 A = B。例如 cccc 存在拆分 A = B = c。

3. 字符串本身也是它的一个子串。

【输入格式】

每个输入文件包含多组数据。输入文件的第一行只有一个整数 T,表示数据 的组数。保证 1 ≤ T ≤ 10。

接下来 T 行,每行包含一个仅由英文小写字母构成的字符串 S,意义如题 所述。

【输出格式】

输出 T 行,每行包含一个整数,表示字符串 S 所有子串的所有拆分中, 总共有多少个是优秀的拆分。

【样例输入】

4
aabbbb
cccccc
aabaabaabaa
bbaabaababaaba

【样例输出】

3 5 4 7

【样例说明】

我们用 S[i, j] 表示字符串 S 第 i 个字符到第 j 个字符的子串(从 1 开 始计数)。

第一组数据中,共有 3 个子串存在优秀的拆分:

S[1,4] = aabb,优秀的拆分为 A = a, B = b;

S[3,6] = bbbb,优秀的拆分为 A = b, B = b;

S[1,6] = aabbbb,优秀的拆分为 A = a, B = bb。

而剩下的子串不存在优秀的拆分,所以第一组数据的答案是 3。

第二组数据中, 有两类,总共 4 个子串存在优秀的拆分:

对于子串 S[1,4] = S[2,5] = S[3,6] = cccc, 它们优秀的拆分相同,均为 A = c, B = c,但由于这些子串位置不同,因此要计算 3 次;

对于子串 S[1,6] = cccccc,它优秀的拆分有 2 种: A = c, B = cc 和 A = cc, B = c,它们是相同子串的不同拆分,也都要计入答案。

所以第二组数据的答案是 3 + 2 = 5。

第三组数据中, S[1,8] 和 S[4,11] 各有 2 种优秀的拆分,其中 S[1,8] 是 问题描述中的例子,所以答案是 2 + 2 = 4。

第四组数据中, S[1,4], S[6,11], S[7,12], S[2,11], S[1,8] 各有 1 种优秀 的拆分, S[3,14] 有 2 种优秀的拆分,所以答案是 5 + 2 = 7。

【更多样例】

下载

【样例 2 输入输出】

见目录下的 excellent/excellent2.in 与 excellent/excellent2.ans。

【样例 3 输入输出】

见目录下的 excellent/excellent3.in 与 excellent/excellent3.ans。

【子任务】

对于全部的测试点,保证 1 ≤ T ≤ 10。 以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的 T 组数据均满足限制条件。

我们假定 n 为字符串 S 的长度,每个测试点的详细数据范围见下表:

【来源】

NOI2016 Day1 T1

  这道题有点难想到正解。

  枚举长度i,然后把字符串拆分成许多连续的长度为i的子串,通过比较LCS与LCP得出一段的答案,这里发现答案是区间加法,考虑用线段树很可能超时,这里用的是差分,看程序很好理解。还有一个地方要注意:更新答案时可能会重复计算,只需要确保每次枚举都只在一段限定的区间更新,就不会出现重叠。

  1 #include <iostream>
  2 #include <cstring>
  3 #include <cstdio>
  4 using namespace std;
  5 const int N=120010;
  6 struct SAM{
  7     char s[N];
  8     int fa[N],pos[N],sa[N],rank[N];
  9     int son[N][26],end[N],rht[N],lcp[N];
 10     int ch[N][26],len[N],id[N],tot;
 11     int od[N],wv[N],lst,cnt;
 12     int mm[N],Min[N][25];
 13     void Init(){
 14         memset(s,0,sizeof(s));
 15         memset(ch,0,sizeof(ch));
 16         memset(end,0,sizeof(end));
 17         memset(son,0,sizeof(son));
 18         memset(pos,0,sizeof(pos));
 19         lst=cnt=1;tot=0;
 20     }
 21
 22     void Insert(int c){
 23         int p=lst,np=lst=++cnt;end[lst]=1;
 24         id[len[np]=len[p]+1]=np;rht[np]=1;
 25         while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
 26         if(!p)fa[np]=1;
 27         else{
 28             int q=ch[p][c],nq;
 29             if(len[q]==len[p]+1)fa[np]=q;
 30             else{
 31                 len[nq=++cnt]=len[p]+1;
 32                 fa[nq]=fa[q];fa[q]=fa[np]=nq;
 33                 memcpy(ch[nq],ch[q],sizeof(ch[q]));
 34                 while(ch[p][c]==q)ch[p][c]=nq,p=fa[p];
 35             }
 36         }
 37     }
 38
 39     void Get_Right(){
 40         for(int i=1;i<=cnt;i++)wv[len[i]]++;
 41         for(int i=1;i<=cnt;i++)wv[i]+=wv[i-1];
 42         for(int i=1;i<=cnt;i++)od[wv[len[i]]--]=i;
 43         for(int i=cnt;i>=1;i--)rht[fa[od[i]]]+=rht[od[i]];
 44     }
 45
 46     void Build_Tree(){
 47         int l=strlen(s+1);
 48         for(int i=l;i>=1;i--)Insert(s[i]-‘a‘);
 49         for(int i=l;i>=1;i--)
 50             for(int x=id[i],p=l+1;x&&!pos[x];x=fa[x])
 51                 p-=len[x]-len[fa[x]],pos[x]=p;
 52         for(int x=2;x<=cnt;x++)son[fa[x]][s[pos[x]]-‘a‘]=x;
 53     }
 54
 55     void DFS(int x,int l){
 56         if(end[x])sa[rank[l-len[x]+1]=++tot]=l-len[x]+1;
 57         for(int i=0;i<26;i++)if(son[x][i])DFS(son[x][i],l);
 58     }
 59
 60     void Build_SA(){
 61         int l=strlen(s+1),k=0;DFS(1,l);
 62         for(int i=1,j;i<=l;lcp[rank[i++]]=k)
 63             for(k?k--:k,j=sa[rank[i]-1];s[i+k]==s[j+k];k++);
 64         mm[0]=-1;
 65         for(int i=1;i<=l;i++){
 66             mm[i]=(i&(i-1))?mm[i-1]:mm[i-1]+1;
 67             Min[i][0]=lcp[i];
 68         }
 69         for(int k=1;k<=mm[l];k++)
 70             for(int i=1;i+(1<<k-1)<=l;i++)
 71                 Min[i][k]=min(Min[i][k-1],Min[i+(1<<(k-1))][k-1]);
 72     }
 73
 74     int LCP(int x,int y){
 75         if(x>y)swap(x,y);x+=1;int k=mm[y-x+1];
 76         int ret=min(Min[x][k],Min[y-(1<<k)+1][k]);
 77         return ret;
 78     }
 79 }A,B;
 80
 81 int ln,T,f[N],g[N];char s[N];
 82 int Get_LCP(int x,int y){return A.LCP(A.rank[x],A.rank[y]);}
 83 int Get_LCS(int x,int y){return B.LCP(B.rank[ln-x+1],B.rank[ln-y+1]);}
 84
 85 int main(){
 86     freopen("excellent.in","r",stdin);
 87     freopen("excellent.out","w",stdout);
 88     scanf("%d",&T);
 89     while(T--){
 90         A.Init();B.Init();
 91         scanf("%s",s+1);ln=strlen(s+1);
 92         for(int i=1;i<=ln;i++){A.s[i]=s[i];B.s[i]=s[ln-i+1];}
 93         A.Build_Tree();A.Build_SA();
 94         B.Build_Tree();B.Build_SA();
 95         for(int i=1;i<=ln;i++)f[i]=g[i]=0;
 96
 97         for(int i=1,l,r,x;i+i<=ln;i++)
 98             for(int j=i;(x=j+i)<=ln;j+=i)if(s[j]==s[x]){
 99                 l=x-Get_LCS(j,x)+1;r=x+Get_LCP(j,x)-1;
100                 l=max(l+i-1,x);r=min(r,x+i-1);
101                 if(l>r)continue;
102                 f[l]++,f[r+1]--;
103                    g[l-i-i+1]++,g[r+1-i-i+1]--;
104             }
105         long long ans=0;
106         for(int i=1;i<=ln;i++)f[i]+=f[i-1],g[i]+=g[i-1];
107         for(int i=1;i<ln;i++)ans+=f[i]*g[i+1];
108         printf("%lld\n",ans);
109     }
110     return 0;
111 }

  

时间: 2024-08-16 21:00:26

字符串(后缀自动机):NOI 2016 优秀的拆分的相关文章

字符串后缀自动机:Directed Acyclic Word Graph

trie -- suffix tree -- suffix automa 有这么一些应用场景: 即时响应用户输入的AJAX搜索框时, 显示候选列表. 搜索引擎的关键字个数统计. 后缀树(Suffix Tree): 从根到叶子表示一个后缀. 仅仅从这一个简单的描述,我们可以概念上解决下面的几个问题: P:查找字符串o是否在字符串S中 A:若o在S中,则o必然是S的某个后缀的前缀. 用S构造后缀树,按在trie中搜索字串的方法搜索o即可. P: 指定字符串T在字符串S中的重复次数. A: 如果T在S

bzoj3756pty的字符串(后缀自动机+计数)

题目描述 题解 我们可以先对trie树建出广义SAM,然后维护一下right集合大小(注意right集合在广义SAM上的维护方式). 然后把匹配穿往广义SAM上匹配,假设现在匹配到了x节点,那么x的所有祖先后可以被匹配上,那么一个节点的贡献即为r[x]*(l[x]-l[fa[x]]). 维护这玩意的和就好了,最下面的节点特判一下. 代码 #include<iostream> #include<cstdio> #include<cstring> #define N 160

[数据结构]后缀自动机

前言 对于字符串 \(s\) ,\(|s|\) 表示s的长度 对于字符集 \(A\) , \(|A|\) 表示 \(A\) 的大小 本文字符串下标一律从0开始. 本文字数较多,如有错别字或者概念性错误,请联系博主或在下方回复. SAM 后缀自动机 (suffix automaton, SAM) 是一种解决多种字符串问题的数据结构. SAM基于一个字符串构建的,是给定字符串的所有子串的压缩形式. 标准定义为: 字符串 \(s\) 的SAM是一个接受 \(s\) 的所有后缀的最小 \(\texttt

poj 2774 最长公共子串--字符串hash或者后缀数组或者后缀自动机

http://poj.org/problem?id=2774 想用后缀数组的看这里:http://blog.csdn.net/u011026968/article/details/22801015 本文主要讲下怎么hash去找 开始的时候写的是O(n^2 logn)算法 果断超时...虽然也用了二分的,, 代码如下: //hash+二分 #include <cstdio> #include <cstring> #include <algorithm> #include

【字符串数据结构后缀系列Part3】后缀自动机的性质和应用

学会了构建SAM之后,我们要开始学如何使用SAM来处理各种问题了. 我们先来整体看一下SAM的性质(引自2015国家集训队论文集张天扬<后缀自动机及其应用>): 1.每个状态s代表的串的长度是区间(lenfas,lens]. 2.对于每个状态s,它代表的所有串在原串中出现次数和每次出现的右端点相同. 3.在后缀自动机的Parent树中,每个状态的right集合都是其父状态right集合的子集. 4.后缀自动机的Parent树是原串的反向前缀树 . 5.两个串的最长公共后缀,位于这两个串对应状态

51nod1469 淋漓字符串(后缀自动机)

题目大意: 首先,我们来定义一下淋漓尽致子串. 1.令原串为S. 2.设子串的长度为len,在原串S中出现的次数为k,令其出现的位置为p1, p2, ....pk(即这个子串在原串中[pi,pi + len - 1]中出现). 3.若k=1,则该子串不是淋漓尽致子串. 4.若存在pi,pj(i != j),使得S[pi - 1] = S[pj - 1],则该子串不是淋漓尽致子串. 5.若存在pi,pj(i != j),使得S[pi + len] = S[pj + len],则该字串不是淋漓尽致字

POJ 1509 Glass Beads 后缀自动机 模板 字符串的最小表示

http://poj.org/problem?id=1509 后缀自动机其实就是一个压缩储存空间时间(对节点重复利用)的储存所有一个字符串所有子串的trie树,如果想不起来长什么样子可以百度一下找个图回忆,从0开始到任意一个点的串都是字符串的子串. 有一些很好用的性质. 字符串的最小表示就是把一个字符串首尾相连再从任意一个地方断开产生的字典序最小的字符串,这个题是求最小表示的开头字母在原字符串中的下标(从1开始). 具体看实现吧,没什么可以解释的地方. 1 #include<iostream>

【BZOJ1396】识别子串&amp;【BZOJ2865】字符串识别(后缀自动机)

[BZOJ1396]识别子串&[BZOJ2865]字符串识别(后缀自动机) 题面 自从有了DBZOJ 终于有地方交权限题了 题解 很明显,只出现了一次的串 在\(SAM\)的\(right/endpos\)集合大小一定为\(1\) 换句话说,在\(parent\)树上是叶子节点 找到所有这样的节点, 假设它的\(len=r\),它父亲的\(len=p\),它的结束位置为显然就是\(r\) 令\(l=r-p\) 以\(r\)结尾, 并且只出现了一次的串的左端点 为\(1..l\),那么,他们的答案

BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机 后缀自动机 字符串

https://www.lydsy.com/JudgeOnline/problem.php?id=3926 广义后缀自动机是一种可以处理好多字符串的一种数据结构(不像后缀自动机只有处理一到两种的时候比较方便). 后缀自动机可以说是一种存子串的缩小点数的trie树,广义后缀自动机就是更改了一下塞点的方式让它可以塞多个子串. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<