题目描述

给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ 、$r$ 、$x$ 、$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数。

$|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ 。


题解

广义后缀自动机+树上倍增+线段树合并

对 $S$ 串和所有 $T_i$ 串的反串放到一起建立广义后缀自动机,得到广义后缀树。

考虑 $S$ 串的 $l...r$ 部分在 $T_i$ 串的出现次数体现为什么:" $S$ 串的 $l...r$ 部分" 在后缀Trie上体现为:顺着 $S$ 的以 $l$ 开头的后缀走到 $S_{l...r}$ 对应节点,该节点是子树内所有后缀的前缀。因此统计的就是该节点子树内有多少个 $T_i$ 的后缀节点。

而现在给出的是后缀树,后缀树相比后缀Trie对无用节点进行压缩,有可能 $S_{l...r}$ 是无用节点。因此要找到的是:最小的 $i\ge r$ ,使得 $S_{l...i}$ 是非无用节点。使用倍增,从底到上求出最靠近根节点的 $dis\ge r-l+1$ 的节点。

问题转化为:求一个点的子树中出现次数最多的颜色是什么。

将询问离线,使用线段树维护子树(right集合)中每种颜色出现的次数,维护区间最大值即最大值位置。DFS整棵树,递归子树后进行线段树合并,最后处理该点对应的询问。

时间复杂度 $O(26n+n\log n)$ 。

  1. #include <vector>
  2. #include <cstdio>
  3. #include <cctype>
  4. #include <cstring>
  5. #include <algorithm>
  6. #define N 1100000
  7. #define lson l , mid , ls[x]
  8. #define rson mid + 1 , r , rs[x]
  9. using namespace std;
  10. typedef pair<int , int> pr;
  11. vector<int> vq[N];
  12. int m , pos[N] , c[N][26] , dis[N] , pre[N] , tot = 1 , last = 1 , head[N] , to[N] , next[N] , cnt , fa[N][22] , deep[N] , log[N] , ls[N * 5] , rs[N * 5] , root[N] , tp , ql[N] , qr[N];
  13. pr mx[N * 5] , ans[N];
  14. char str[N];
  15. void extend(int x)
  16. {
  17. int p = last;
  18. if(c[p][x])
  19. {
  20. int q = c[p][x];
  21. if(dis[q] == dis[p] + 1) last = q;
  22. else
  23. {
  24. int nq = ++tot;
  25. memcpy(c[nq] , c[q] , sizeof(c[q]));
  26. dis[nq] = dis[p] + 1 , pre[nq] = pre[q] , last = pre[q] = nq;
  27. while(p && c[p][x] == q) c[p][x] = nq , p = pre[p];
  28. }
  29. }
  30. else
  31. {
  32. int np = last = ++tot;
  33. dis[np] = dis[p] + 1;
  34. while(p && !c[p][x]) c[p][x] = np , p = pre[p];
  35. if(!p) pre[np] = 1;
  36. else
  37. {
  38. int q = c[p][x];
  39. if(dis[q] == dis[p] + 1) pre[np] = q;
  40. else
  41. {
  42. int nq = ++tot;
  43. memcpy(c[nq] , c[q] , sizeof(c[q]));
  44. dis[nq] = dis[p] + 1 , pre[nq] = pre[q] , pre[np] = pre[q] = nq;
  45. while(p && c[p][x] == q) c[p][x] = nq , p = pre[p];
  46. }
  47. }
  48. }
  49. }
  50. inline void add(int x , int y)
  51. {
  52. to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
  53. }
  54. void dfs(int x)
  55. {
  56. int i;
  57. for(i = 1 ; i <= log[deep[x]] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
  58. for(i = head[x] ; i ; i = next[i]) fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
  59. }
  60. int find(int x , int d)
  61. {
  62. int i;
  63. for(i = log[deep[x]] ; ~i ; i -- )
  64. if((1 << i) <= deep[x] && dis[fa[x][i]] >= d)
  65. x = fa[x][i];
  66. return x;
  67. }
  68. inline void pushup(int x)
  69. {
  70. mx[x] = max(mx[ls[x]] , mx[rs[x]]);
  71. }
  72. void insert(int p , int l , int r , int &x)
  73. {
  74. if(!x) x = ++tp;
  75. if(l == r)
  76. {
  77. mx[x].first ++ , mx[x].second = -p;
  78. return;
  79. }
  80. int mid = (l + r) >> 1;
  81. if(p <= mid) insert(p , lson);
  82. else insert(p , rson);
  83. pushup(x);
  84. }
  85. int merge(int l , int r , int x , int y)
  86. {
  87. if(!x) return y;
  88. if(!y) return x;
  89. if(l == r)
  90. {
  91. mx[x].first += mx[y].first;
  92. return x;
  93. }
  94. int mid = (l + r) >> 1;
  95. ls[x] = merge(l , mid , ls[x] , ls[y]);
  96. rs[x] = merge(mid + 1 , r , rs[x] , rs[y]);
  97. pushup(x);
  98. return x;
  99. }
  100. pr query(int b , int e , int l , int r , int x)
  101. {
  102. if(b <= l && r <= e) return mx[x];
  103. int mid = (l + r) >> 1;
  104. if(e <= mid) return query(b , e , lson);
  105. else if(b > mid) return query(b , e , rson);
  106. else return max(query(b , e , lson) , query(b , e , rson));
  107. }
  108. void solve(int x)
  109. {
  110. int i;
  111. for(i = head[x] ; i ; i = next[i]) solve(to[i]) , root[x] = merge(1 , m , root[x] , root[to[i]]);
  112. for(i = 0 ; i < (int)vq[x].size() ; i ++ ) ans[vq[x][i]] = query(ql[vq[x][i]] , qr[vq[x][i]] , 1 , m , root[x]);
  113. }
  114. inline char nc()
  115. {
  116. static char buf[100000] , *p1 , *p2;
  117. return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ;
  118. }
  119. inline int readnum()
  120. {
  121. int ret = 0; char ch = nc();
  122. while(!isdigit(ch)) ch = nc();
  123. while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ '0') , ch = nc();
  124. return ret;
  125. }
  126. inline int readstr(char *p)
  127. {
  128. char ch = nc() , *q = p;
  129. while(isalpha(ch)) *q ++ = ch , ch = nc();
  130. return q - p;
  131. }
  132. char pbuf[10000000] , *pp = pbuf;
  133. inline void write(int x)
  134. {
  135. static int sta[20];
  136. int top = 0;
  137. if(!x) sta[top ++ ] = 0;
  138. while(x) sta[top ++ ] = x % 10 , x /= 10;
  139. while(top -- ) *pp ++ = sta[top] ^ '0';
  140. }
  141. int main()
  142. {
  143. int q , i , j , x , y;
  144. for(i = readstr(str + 1) ; i ; i -- ) extend(str[i] - 'a') , pos[i] = last;
  145. m = readnum();
  146. for(i = 1 ; i <= m ; i ++ )
  147. {
  148. last = 1;
  149. for(j = readstr(str + 1) ; j ; j -- )
  150. extend(str[j] - 'a') , insert(i , 1 , m , root[last]);
  151. }
  152. for(i = 2 ; i <= tot ; i ++ ) add(pre[i] , i) , log[i] = log[i >> 1] + 1;
  153. dfs(1);
  154. q = readnum();
  155. for(i = 1 ; i <= q ; i ++ )
  156. {
  157. ql[i] = readnum() , qr[i] = readnum() , x = readnum() , y = readnum();
  158. vq[find(pos[x] , y - x + 1)].push_back(i);
  159. }
  160. solve(1);
  161. for(i = 1 ; i <= q ; i ++ ) write(ans[i].first ? -ans[i].second : ql[i]) , *pp ++ = ' ' , write(ans[i].first) , *pp ++ = '\n';
  162. fwrite(pbuf , 1 , pp - pbuf , stdout);
  163. return 0;
  164. }

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并的更多相关文章

  1. CF547E Milk and Friends(AC自动机的fail指针上建主席树 或 广义后缀自动机的parent线段树合并)

    What-The-Fatherland is a strange country! All phone numbers there are strings consisting of lowercas ...

  2. [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

    题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...

  3. CF666E Forensic Examination 广义后缀自动机_线段树合并_树上倍增

    题意: 给定一个串 $S$ 和若干个串 $T_{i}$每次询问 $S[pl..pr]$ 在 $Tl..Tr$ 中出现的最多次数,以及出现次数最多的那个串的编号. 数据范围: 需要离线 题解:首先,很常 ...

  4. 【CF666E】Forensic Examination 广义后缀自动机+倍增+线段树合并

    [CF666E]Forensic Examination 题意:给你一个字符串s和一个字符串集合$\{t_i\}$.有q个询问,每次给出$l,r,p_l,p_r$,问$s[p_l,p_r]$在$t_l ...

  5. CF 666E Forensic Examination——广义后缀自动机+线段树合并

    题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...

  6. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  7. 【CF666E】Forensic Examination - 广义后缀自动机+线段树合并

    广义SAM专题的最后一题了……呼 题意: 给出一个长度为$n$的串$S$和$m$个串$T_{1\cdots m}$,给出$q$个询问$l,r,pl,pr$,询问$S[pl\cdots pr]$在$T_ ...

  8. CF1037H Security 后缀自动机 + right集合线段树合并 + 贪心

    题目描述: 给定一个字符串 $S$ 给出 $Q$ 个操作,给出 $L,R,T$,求出字典序最小的 $S_{1}$ 为 $S[L...R]$的子串,且 $S_{1}$ 的字典序严格大于 $T$. 输出这 ...

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

    题目链接 \(Description\) 给定串\(S\)和\(m\)个串\(T_i\).\(Q\)次询问,每次询问\(l,r,p_l,p_r\),求\(S[p_l\sim p_r]\)在\(T_l\ ...

随机推荐

  1. 《Java程序设计》第2周学习总结(Markdown语法修改版)

    20175105 2018-2019-2 <Java程序设计>第2周学习总结 Vim操作的一些总结 这些天通过学习,对于vim的操作有了很大的提升,下面我把vim的比较常见的操作方式做了归 ...

  2. 树莓派学习笔记(2):常用linux命令

    转载请注明:@小五义http://www.cnblogs.com/xiaowuyi 1.ls命令:列出文件目录的常用命令,主要参数见下表. -a 列出目录下的所有文件,包括以.开头的隐含文件. -b ...

  3. kubespray -- 快速部署高可用k8s集群 + 扩容节点 scale.yaml

    主机 系统版本      配置       ip Mater.Node,ansible CentOS 7.2                                             4 ...

  4. cache-fusion笔记

    GRD  (global resource directory)保存着所有实例中资源的分布情况 GCS  (global cache service)具体执行cache fusion 工作的服务,对应 ...

  5. 20155320 Exp9 Web安全基础

    20155320 Exp9 Web安全基础 [实验后回答问题] (1)SQL注入攻击原理,如何防御 SQL注入攻击就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗 ...

  6. 执行力:Just Do It

    执行力,最最关键的一点就是,立即去做,不要想太多. 当有一件事需要去做的时候,你的大脑肯定是接受到了"某种信号",比如来了一个灵感.受到一点启发.做某件事突然来了兴趣.或者想去探讨 ...

  7. PostgreSQL同步方案

    Windows下Postgre SQL数据库通过Slony-I实现数据库双机同步备份 - 数据库其他综合 - 红黑联盟 postgresql同步流复制的Hot Standby - CSDN博客   使 ...

  8. CS190.1x-ML_lab3_linear_reg_student

    这次作业主要是有关监督学习,数据集是来自UCI Machine Learning Repository的Million Song Dataset.我们的目的是训练一个线性回归的模型来预测一首歌的发行年 ...

  9. REST-framework快速构建API--认证

    一.API使用流程 使用过API的同学都知道,我们不可能任意调用人家的API,因为通过API可以获取很多关键数据,而且这个API可能供多个部门或个人使用,所以必须是经过授权的用户才能调用. API的使 ...

  10. stl源码剖析 详细学习笔记deque(1)

    //--------------------------15/3/12---------------------------- deque { deque没有容量(capacity)观念,是动态分段的 ...