Description

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

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

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

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

Input

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

Output

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

Sample Input

  1. scbamgepe
  2. 3
  3. smape 2 7
  4. sbape 3 8
  5. sgepe 1 9

Sample Output

  1. 12
  2. 10
  3. 4

Solution

考虑\(l=1,r=|S|\)怎么做,先对\(S\)和\(T\)都建出后缀自动机,然后设\(lim_i\)表示字符串\(T\)的第\(i\)位能和\(S\)匹配到的最长的在\(T\)串以\(i\)为最后一个点的后缀是多少,也就是\(T[x...i]=S'\),其中\(S'\)为\(S\)的任意一个子串,\(lim_i=\max\{i-x+1\}\)。

设\(pos[i]\)表示\(T\)的后缀自动机节点\(i\)的\(right\)集合最小的那个点。

那么如果能预处理这些东西,答案很显然就是:

\[ans=\sum_{i=2}^{cnt}\max(0,maxl_i-\max(lim_{pos_i},maxl_{par_i}))
\]

这个式子的意思也很显然,就是考虑\(T\)的\(SAM\)每个节点的贡献就好了。

考虑如何算\(lim_i\),当前情况直接吧串\(T\)扔到\(S\)的后缀自动机上跑,然后失配了就跳\(parent\)就好了。

那么满分做法就很显然了,对于\(S\)的自动机上每个点开一个线段树来存\(right\)集合的分布情况,这个可以线段树合并搞搞,然后对于询问的\([l,r]\)区间,处理\(lim_i\)的时候,设当前的匹配长度是\(len\),那么跳\(parent\)时判断一下有没有一个\(right\)集合的元素在区间\([l+len,r]\)之内,如果有就匹配上,没有就继续跳。

答案还是和上面一样算。

复杂度为\(O(|S|\log |S|+\sum|T|\log |S|)\)。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. void read(int &x) {
  4. x=0;int f=1;char ch=getchar();
  5. for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
  6. for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
  7. }
  8. #define ll long long
  9. void print(ll x) {
  10. if(x<0) putchar('-'),x=-x;
  11. if(!x) return ;print(x/10),putchar(x%10+48);
  12. }
  13. void write(ll x) {if(!x) putchar('0');else print(x);putchar('\n');}
  14. const int maxn = 1e6+10;
  15. const int maxm = 2e7+10;
  16. char s[maxn];
  17. int rt[maxn],n,m;
  18. // ======== Segment_Tree
  19. #define mid ((l+r)>>1)
  20. int SGT,ls[maxm],rs[maxm];
  21. void modify(int &p,int l,int r,int x) {
  22. if(!p) p=++SGT;
  23. if(l==r) return ;
  24. if(x<=mid) modify(ls[p],l,mid,x);
  25. else modify(rs[p],mid+1,r,x);
  26. }
  27. int query(int p,int l,int r,int x,int y) {
  28. if(!p||x>y) return 0;
  29. if(x<=l&&r<=y) return 1;
  30. if(x<=mid&&query(ls[p],l,mid,x,y)) return 1;
  31. if(y>mid&&query(rs[p],mid+1,r,x,y)) return 1;
  32. return 0;
  33. }
  34. int merge(int x,int y) {
  35. if(!x||!y) return x+y;
  36. int p=++SGT;
  37. ls[p]=merge(ls[x],ls[y]);
  38. rs[p]=merge(rs[x],rs[y]);
  39. return p;
  40. }
  41. // ========
  42. namespace sam { // string S 's Suffix Automaton
  43. int cnt=1,qs=1,lstp=1;
  44. int tr[maxn][27],par[maxn],ml[maxn],vis[maxn],t[maxn],r[maxn];
  45. void append(int x) {
  46. int p=lstp,np=++cnt;ml[np]=ml[p]+1,vis[np]=1;lstp=np;
  47. for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
  48. if(!p) return par[np]=qs,void();
  49. int q=tr[p][x];
  50. if(ml[p]+1<ml[q]) {
  51. int nq=++cnt;ml[nq]=ml[p]+1;
  52. memcpy(tr[nq],tr[q],sizeof tr[nq]);
  53. par[nq]=par[q],par[q]=par[np]=nq;
  54. for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
  55. } else par[np]=q;
  56. }
  57. void prepare() {
  58. scanf("%s",s+1);n=strlen(s+1);
  59. for(int i=1;i<=n;i++) append(s[i]-'a'+1);
  60. for(int i=1;i<=cnt;i++) t[ml[i]]++;
  61. for(int i=1;i<=n;i++) t[i]+=t[i-1];
  62. for(int i=1;i<=cnt;i++) r[t[ml[i]]--]=i; //toposort
  63. for(int i=cnt;i;i--) {
  64. if(vis[r[i]]) modify(rt[r[i]],1,n,ml[r[i]]);
  65. if(par[r[i]]) rt[par[r[i]]]=merge(rt[par[r[i]]],rt[r[i]]);
  66. }
  67. }
  68. }
  69. namespace sam2 { //string T 's Suffix Automaton
  70. int cnt,qs=1,lstp;
  71. int tr[maxn][27],ml[maxn],pos[maxn],par[maxn],lim[maxn];
  72. void init() {cnt=lstp=1;memset(tr[1],0,sizeof tr[1]);}
  73. int newnode(int x) {ml[++cnt]=x;memset(tr[cnt],0,sizeof tr[cnt]),par[cnt]=0;return cnt;}
  74. void append(int x) {
  75. int p=lstp,np=newnode(ml[p]+1);pos[np]=ml[np];lstp=np;
  76. for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
  77. if(!p) return par[np]=qs,void();
  78. int q=tr[p][x];
  79. if(ml[p]+1<ml[q]) {
  80. int nq=newnode(ml[p]+1);pos[nq]=pos[q];
  81. memcpy(tr[nq],tr[q],sizeof tr[nq]);
  82. par[nq]=par[q],par[q]=par[np]=nq;
  83. for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
  84. } else par[np]=q;
  85. }
  86. ll solve() {
  87. init();int L,R;
  88. scanf("%s",s+1);m=strlen(s+1);read(L),read(R);
  89. int now=qs,len=0;
  90. for(int i=1;i<=m;i++) {
  91. int x=s[i]-'a'+1;
  92. append(x);
  93. while(1) { // run on automaton 1
  94. if(sam :: tr[now][x]&&query(rt[sam ::tr[now][x]],1,n,L+len,R)) {
  95. now=sam :: tr[now][x],len++;break;
  96. }
  97. if(!len) break;len--;
  98. if(len==sam :: ml[sam :: par[now]]) now=sam :: par[now];
  99. }lim[i]=len;
  100. }
  101. ll ans=0;
  102. for(int i=2;i<=cnt;i++) ans+=max(0,ml[i]-max(lim[pos[i]],ml[par[i]]));
  103. return ans;
  104. }
  105. }
  106. int main() {
  107. sam :: prepare();
  108. int T;read(T);while(T--) write(sam2 :: solve());
  109. return 0;
  110. }

[BZOJ5417] [NOI2018]你的名字的更多相关文章

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

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

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

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

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

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

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

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

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

    看到题目名字去补番是种怎么样的体验 我只会 \(68\) 分,打了个暴力.正解看了一会儿,发现跟 \([HEOI2016/TJOI2016]\) 字符串很像,用线段树合并维护 \(endpos\) 集 ...

  6. [NOI2018]你的名字(68pts) 后缀自动机

    讲解在满分做法的博客中 Code: #include <cstdio> #include <algorithm> #include <cstring> #defin ...

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

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

  8. #P4770 [NOI2018]你的名字 的题解

    题目背景 实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题. 题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外 ...

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

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

随机推荐

  1. 【PBR的基本配置】

    PBR基于策略路由的配置 一:基于报文协议的本地PBR 1:首先进行理论分析:在SW1上利用基于报文报文协议类型的PBR,在sw1与sw3的连接链路上,利用acl制定允许tcp的报文通过3000,并与 ...

  2. 用C#实现WEB代理服务器

    用C#实现Web代理服务器 代理服务程序是一种广泛使用的网络应用程序.代理程序的种类非常多,根据协议不同可以分成HTTP代理服务程序.FTP代理服务程序等,而运行代理服务程序的服务器也就相应称为HTT ...

  3. [转]JavaScript中的匿名函数及函数的闭包

    JavaScript中的匿名函数及函数的闭包  原文地址:http://www.cnblogs.com/wl0000-03/p/6050108.html 1.匿名函数 函数是JavaScript中最灵 ...

  4. hadoop生态搭建(3节点)-01.基础配置

    # 基础配置# ==================================================================node1 vi /etc/hostname nod ...

  5. Redis缓存数据库的安装与配置(3)

    3 Redis主动同步设置方法 Redis主从同步 1.Redis主从同步特点 一个master可以拥有多个slave 多个slave可以连接同一个master,还可以连接到其他slave 主从复制不 ...

  6. xampps 不能配置非安装目录虚拟主机解决方案

    今天将前几天安装好的xampps配置下,准备开始php开发之旅,在我信心满满的将工作目录定在非安装目录上(安装目录在:D:\Program Files\xampps\apache\htdocs  我将 ...

  7. Jersey2+swagger组建restful风格api及文档管理

    1.jar包引入 <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId& ...

  8. git初始化仓库相关

    当我们需要新建一个git项目会遇到的问题 全局设置 git config --global user.name "名字" git config --global user.emai ...

  9. P1346 电车(dijkstra)

    P1346 电车 题目描述 在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能).在每个路口 ...

  10. nginx https ssl 配置

    #设置https 访问server { listen ; server_name www.xxx.com; access_log xxx/xxx/xxx.log combined; index ind ...