题目背景

实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题。

题目描述

小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。

由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

输入格式

第一行一个字符串S ,之后询问给出的ION2017 的命名串都是S 的连续子串。 第二行一个正整数Q,表示询问次数。 接下来Q 行,每行有一个字符串T 和两个正整数l,rl,r,表示询问如果ION2017 的 命名串是S [l..r]S[l..r],ION2018 的命名串是T 的话,有几种命名方式一定满足规定。

输出格式

输出Q 行,第i 行一个非负整数表示第i 个询问的答案。

输入输出样例

输入

  1. scbamgepe
  2.  
  3. smape
  4. sbape
  5. sgepe

输出 

  1.  

题解

  • 分析

我们考虑对于一个询问,先求出TT中本质不同的子串个数,然后减去是S(l,r)S(l,r)子串的。

本质不同的子串个数,直接用height数组的性质就可以求。

我们考虑,对于TT的每一个后缀,求出其最长的前缀长度LL,使得该后缀长度为LL的前缀是S(l,r)S(l,r)的子串。

把SS和所有TT连起来建后缀数组。为了方便计算每个询问,我们用链表的方法,记录每个位置在同一个串中的前驱后继。

然后我们按顺序考虑TT的每一个后缀。

如果aa位置的后缀的满足条件的最长前缀为LL,,则a+1a+1位置的至少为L-1L−1。原理和求height数组相同,两边都同时去掉第一个字符,至少还留下L-1L−1。

所以考虑每个位置的话,用双指针扫描一下即可。

然后,假如我们现在考虑位置aa的长度为LL的前缀是否可行,就是相当于在SS的[l,r-L+1][l,r−L+1]内找一个位置bb,满足LCP(a,b)\geqslant LLCP(a,b)⩾L。

满足LCP(a,b)\geqslant LLCP(a,b)⩾L条件的在后缀数组上的区间[ll,rr][ll,rr],可以二分,配合height数组的ST表在O(\log n)O(logn)的时间内求出。

然后问题转化为询问在后缀数组上的区间[ll,rr][ll,rr]内,是否存在一个在SS的[l,r-l+1][l,r−l+1]中的字符。这个问题可以用建主席树然后询问,单次查询时间复杂度O(\log n)O(logn)。

对于一个位置,它与后缀数组上一个位置可能会算重,所以要减掉它们的LCPLCP。由于两个位置可能不连续,这部分也要用ST表来查。

所以总时间复杂度O(n\log n)O(nlogn)。n=|S|+\sum|T|n=∣S∣+∑∣T∣。

  • 代码

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. #define N 500505
  5. #define M 1800505
  6. #define reg register
  7. #define lg2(x)(31-__builtin_clz(x))
  8. typedef long long LL;
  9. int n,sa[M],height[M],x[M],s[M],mgk,bel[M],m,QL[N],QR[N],y[M],st[][M],nxt[M],head[N],node=,rt[M],pp[M],len[N];
  10. int ls[M*],rs[M*],sz[M*];
  11. char ss[N];
  12. bool isk;
  13. int _L,_R;
  14. void add(int&o,int pr,int l,int r,int pos){
  15. sz[o=++node]=sz[pr]+;
  16. if(l<r){
  17. const int mid=l+r>>;
  18. if(pos<=mid)add(ls[o],ls[pr],l,mid,pos),rs[o]=rs[pr];else add(rs[o],rs[pr],mid+,r,pos),ls[o]=ls[pr];
  19. }
  20. }
  21. void sort(){
  22. int m=mgk,c[M];
  23. for(int i=;i<=m;++i)c[i]=;
  24. for(int i=;i<=n;++i)++c[x[i]=s[i]];
  25. for(int i=;i<=m;++i)c[i]+=c[i-];
  26. for(int i=n;i;--i)sa[c[x[i]]--]=i;
  27. for(int k=,p;k<=n;k<<=){
  28. p=;
  29. for(int i=n-k+;i<=n;++i)y[++p]=i;
  30. for(reg int i=;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
  31. for(reg int i=;i<=m;++i)c[i]=;
  32. for(reg int i=;i<=n;++i)++c[x[i]];
  33. for(reg int i=;i<=m;++i)c[i]+=c[i-];
  34. for(reg int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
  35. std::swap(x,y);
  36. x[sa[]]=p=;
  37. for(int i=;i<=n;++i)
  38. x[sa[i]]=y[sa[i]]==y[sa[i-]]&&y[sa[i]+k]==y[sa[i-]+k]?p:++p;
  39. if(p==n)break;
  40. m=p;
  41. }
  42. for(int i=,k=;i<=n;++i)
  43. if(x[i]>){
  44. k-=!!k;
  45. const int j=sa[x[i]-];
  46. while(s[i+k]==s[j+k])++k;
  47. height[x[i]]=k;
  48. }
  49. }
  50. inline int find(int l,int r){
  51. if(l>r)return n;
  52. const int lg=lg2(r-l+);
  53. return std::min(st[lg][l],st[lg][r-(<<lg)+]);
  54. }
  55. void init(){
  56. for(reg int i=;i<=n;++i)st[][i]=height[i];
  57. for(int i=;i<;++i)
  58. for(reg int j=;j<=n;++j)
  59. if(j+(<<i)<=n)st[i+][j]=std::min(st[i][j],st[i][j+(<<i)]);else break;
  60. int pre[N];
  61. memset(pre,,sizeof pre);
  62. for(int i=;i<=n;++i)
  63. if(s[sa[i]]<='z'){
  64. if(pre[bel[sa[i]]])
  65. nxt[pre[bel[sa[i]]]]=i,pp[i]=pre[bel[sa[i]]];else head[bel[sa[i]]]=i;
  66. pre[bel[sa[i]]]=i;
  67. }
  68. }
  69. void query(const int&ri,const int&le,int l,int r){
  70. if(sz[ri]==sz[le]||isk)return;
  71. if(_L<=l&&r<=_R)return(void)(isk=);
  72. const int mid=l+r>>;
  73. if(_L<=mid)query(ls[ri],ls[le],l,mid);
  74. if(mid<_R&&!isk)query(rs[ri],rs[le],mid+,r);
  75. }
  76. bool check(int pos,int len,int l,int r){
  77. int L,ll,rr;
  78. ll=,rr=pos-;
  79. while(ll<=rr){
  80. const int mid=ll+rr>>;
  81. if(find(mid+,pos)>=len)rr=mid-;else ll=mid+;
  82. }
  83. L=rr;
  84. ll=pos+,rr=n;
  85. while(ll<=rr){
  86. const int mid=ll+rr>>;
  87. if(find(pos+,mid)>=len)ll=mid+;else rr=mid-;
  88. }
  89. isk=;
  90. _L=l,_R=r;
  91. query(rt[ll-],rt[L],,n);
  92. return isk;
  93. }
  94. int main(){
  95. memset(bel,-,sizeof bel);
  96. mgk='z'+;
  97. scanf("%s",ss);
  98. for(int i=;ss[i];++i)
  99. s[++n]=ss[i],bel[n]=;
  100. s[++n]=mgk++;
  101. scanf("%d",&m);
  102. for(int i=;i<=m;++i){
  103. scanf("%s%d%d",ss,QL+i,QR+i);
  104. for(int j=;ss[j];++j)
  105. s[++n]=ss[j],bel[n]=i;
  106. len[i]=n;
  107. s[++n]=mgk++;
  108. }
  109. sort();
  110. init();
  111. for(reg int i=;i<=n;++i)
  112. if(!bel[sa[i]])add(rt[i],rt[i-],,n,sa[i]);else rt[i]=rt[i-];
  113. for(int i=;i<=m;++i){
  114. LL ans=len[i]-sa[head[i]]+;
  115. int mnid=sa[head[i]];
  116. for(int j=nxt[head[i]];j;j=nxt[j]){
  117. ans+=len[i]-sa[j]+;
  118. ans-=find(pp[j]+,j);
  119. mnid=std::min(mnid,sa[j]);
  120. }
  121. for(int j=mnid,L=mnid;s[j]<='z';++j){
  122. if(L<j)L=j;
  123. while(s[L]<='z'&&check(x[j],L-j+,QL[i],QR[i]-L+j))++L;
  124. if(x[j]!=head[i])ans-=std::max(L-j-find(pp[x[j]]+,x[j]),);else
  125. ans-=L-j;
  126. }
  127. printf("%lld\n",ans);
  128. }
  129. return ;
  130. }

#P4770 [NOI2018]你的名字 的题解的更多相关文章

  1. UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)

    NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...

  2. 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)

    传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ...

  3. 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]

    传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...

  4. luogu P4770 [NOI2018]你的名字

    传送门 upd 19.4.24: WC这个做法真的有问题,不往回跳会WA是因为一开始跳到了S[1...l-1]所对应的点,然后往后接字符的时候可能会因为不在正确的endpos中,然后往回跳过头,其实一 ...

  5. P4770 [NOI2018]你的名字

    蒟蒻表示不会sam凉凉了,所以只能提高SA技巧? 题意:有一个串\(A\),每次选择一个\(A\)的子串\(A'\),以及串\(B\),问\(B\)的所有本质不同子串中不在\(A'\)中的串的数量. ...

  6. 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)

    [BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...

  7. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  8. BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并

    题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...

  9. [NOI2018]你的名字(后缀自动机+线段树)

    题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...

随机推荐

  1. rabbitmq 命令行与控制台

    命令行和管控台 rabbitmqctl stop_app 关闭应用 rabbitmqctl start_app 打开应用 rabbitmqctl status 节点状态 rabbitmqctl add ...

  2. 微信小程序 - height: calc(xx - xx);无效

    遇到一个小问题,记录一下 问题:在微信小程序中使用scroll-view标签时,用height:cale(xx - xx)设置高度无效,在page中设置高度为百分百依旧无效 解决办法:直接在父级vie ...

  3. IntelliJ IDEA 2017.3尚硅谷-----设置字体大小行间距

  4. python开发基础04-列表、元组、字典操作练习

    练习1: # l1 = [11,22,33]# l2 = [22,33,44]# a. 获取内容相同的元素列表# b. 获取 l1 中有, l2 中没有的元素列表# c. 获取 l2 中有, l1 中 ...

  5. docker里面安装sqlserver2017

    首先要注意,docker一般不做数据持久化容器.如果非要安装可以参考微软官方教程: https://docs.microsoft.com/zh-cn/sql/linux/quickstart-inst ...

  6. C# 委托实例实现的多种类型

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. PHP array_chunk() 妙用

    定义和用法 array_chunk()函数把一个数组分割为新的数组块. array_chunk(array,size,preserve_keys); 参数 描述 array 必需.规定要使用的数组. ...

  8. Java中List集合的逆序排列

    Collections.reverse(list);  //实现List集合逆序排列

  9. libcurl库的简介(一)

    一.Libcurl库简介 LibCurl是免费的客户端URL传输库,支持FTP,FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, FILE ,LDAP ...

  10. Codeforces Round #620 (Div. 2) E

    LCA的倍增 模板: ], depth[maxn]; int dist[maxn],head[maxn]; void add(int u,int v,int dist0){ a[tot].next=h ...