题解

我们冷静一下,先画一棵后缀树

然后发现我们要给c和d这一段区间在[a,b]这一段开头的串里找lcp

而lcp呢,就是c点的祖先的到根的一段,假如这个祖先的子树里有[a,b - dis[u] + 1],那么这个u就是合法的,维护每个点子树里出现过的后缀串起点可以用线段树合并

(这里的深度指后缀树上该点到根的距离)

我们先用min(b - a + 1,c - d + 1)限制一下这个祖先的深度,选择距离根深度最近且深度>= min(b - a + 1,c - d + 1),找的这个点称为p

那么我们要找最长的合法的部分,可以二分,二分的log,倍增找点的log,线段树里判断是否合法的log,一共是三个log,当然,我们冷静一下,显然有些log常数非常小嘛

但是我们再冷静一下,我们可以发现,我们要找的点可以当做c的祖先中,深度最小的,且子树中不存在[a,b - dis[u] + 1]的点u,我们可以从高位到低位枚举二进制位,按位判断让p跳还是不跳,这就是两个log了

同时这个高位到低位枚举二进制位也可用于找到p

那么这个时候难道p的父亲的深度就是答案了吗?too naive

是p的深度到p的父亲的深度都可能,我们需要再次二分,二分的左边界p的父亲的深度,右边界是min(p的深度 - 1,min(b - a + 1,d - c + 1))

复杂度最后还是\(O(n \log^2 n)\)

代码

  1. #include <bits/stdc++.h>
  2. #define enter putchar('\n')
  3. #define space putchar(' ')
  4. #define pii pair<int,int>
  5. #define fi first
  6. #define se second
  7. #define MAXN 200005
  8. #define pb push_back
  9. #define mp make_pair
  10. //#define ivorysi
  11. using namespace std;
  12. typedef long long int64;
  13. typedef double db;
  14. template<class T>
  15. void read(T &res) {
  16. res = 0;T f = 1;char c = getchar();
  17. while(c < '0' || c > '9') {
  18. if(c == '-') f = -1;
  19. c = getchar();
  20. }
  21. while(c >= '0' && c <= '9') {
  22. res = res * 10 + c - '0';
  23. c = getchar();
  24. }
  25. }
  26. template<class T>
  27. void out(T x) {
  28. if(x < 0) {x = -x;putchar('-');}
  29. if(x >= 10) out(x / 10);
  30. putchar('0' + x % 10);
  31. }
  32. int N,M;
  33. char s[MAXN];
  34. int id[MAXN];
  35. struct node {
  36. int to,next,val;
  37. }E[MAXN * 4];
  38. int head[MAXN],sumE,Ncnt,fa[MAXN][20],dis[MAXN],pos[MAXN];
  39. void add(int u,int v,int c) {E[++sumE].to = v;E[sumE].next = head[u];E[sumE].val = c;head[u] = sumE;}
  40. namespace SAM {
  41. struct node {
  42. node *nxt[26],*par;
  43. int len,cnt;
  44. }pool[MAXN * 2],*tail = pool,*root,*last,*que[MAXN * 2];
  45. int c[MAXN];
  46. void Init() {
  47. root = last = tail++;
  48. }
  49. void build_sam(int l,int c) {
  50. node *nowp = tail++,*p;
  51. nowp->len = l;nowp->cnt = 1;
  52. for(p = last ; p && !p->nxt[c]; p = p->par) {
  53. p->nxt[c] = nowp;
  54. }
  55. if(!p) nowp->par = root;
  56. else {
  57. node *q = p->nxt[c];
  58. if(q->len == p->len + 1) nowp->par = q;
  59. else {
  60. node *copyq = tail++;
  61. *copyq = *q;
  62. copyq->len = p->len + 1;copyq->cnt = 0;
  63. q->par = nowp->par = copyq;
  64. for(; p && p->nxt[c] == q ; p = p->par) {
  65. p->nxt[c] = copyq;
  66. }
  67. }
  68. }
  69. last = nowp;
  70. }
  71. void build_suffix_tree() {
  72. Ncnt = tail - pool;
  73. for(int i = 0 ; i < Ncnt ; ++i) {
  74. c[pool[i].len]++;
  75. }
  76. for(int i = 1 ; i <= N ; ++i) c[i] += c[i - 1];
  77. for(int i = 0 ; i < Ncnt ; ++i) {
  78. que[c[pool[i].len]--] = &pool[i];
  79. }
  80. for(int i = 1 ; i <= Ncnt ; ++i) {
  81. int u = que[i] - pool + 1;
  82. if(que[i]->par) {
  83. int f = que[i]->par - pool + 1;
  84. fa[u][0] = f;
  85. add(f,u,que[i]->len - que[i]->par->len);
  86. add(u,f,que[i]->len - que[i]->par->len);
  87. }
  88. if(que[i]->cnt) {id[N - que[i]->len + 1] = u;pos[u] = N - que[i]->len + 1;}
  89. }
  90. }
  91. }
  92. namespace seg_tr {
  93. struct node {
  94. int lc,rc;
  95. }tr[MAXN * 80];
  96. int rt[MAXN],Ncnt;
  97. void Insert(int x,int &u,int L,int R,int pos) {
  98. u = ++Ncnt;
  99. tr[u] = tr[x];
  100. if(L == R) return;
  101. int mid = (L + R) >> 1;
  102. if(pos <= mid) Insert(tr[x].lc,tr[u].lc,L,mid,pos);
  103. else Insert(tr[x].rc,tr[u].rc,mid + 1,R,pos);
  104. }
  105. int Merge(int Lt,int Rt) {
  106. if(!Lt) return Rt;
  107. if(!Rt) return Lt;
  108. int u = ++Ncnt;
  109. tr[u].lc = Merge(tr[Lt].lc,tr[Rt].lc);
  110. tr[u].rc = Merge(tr[Lt].rc,tr[Rt].rc);
  111. return u;
  112. }
  113. bool Query(int u,int L,int R,int l,int r) {
  114. if(!u) return false;
  115. if(L == l && R == r) return 1;
  116. int mid = (L + R) >> 1;
  117. if(r <= mid) return Query(tr[u].lc,L,mid,l,r);
  118. else if(l > mid) return Query(tr[u].rc,mid + 1,R,l,r);
  119. else return Query(tr[u].lc,L,mid,l,mid) || Query(tr[u].rc,mid + 1,R,mid + 1,r);
  120. }
  121. }
  122. using seg_tr::rt;
  123. using seg_tr::Merge;
  124. using seg_tr::Insert;
  125. using seg_tr::Query;
  126. void dfs(int u) {
  127. if(pos[u]) {Insert(rt[u],rt[u],1,N,pos[u]);}
  128. for(int i = head[u] ; i ; i = E[i].next) {
  129. int v = E[i].to;
  130. if(v != fa[u][0]) {
  131. dis[v] = dis[u] + E[i].val;
  132. dfs(v);
  133. rt[u] = Merge(rt[u],rt[v]);
  134. }
  135. }
  136. }
  137. void Init() {
  138. read(N);read(M);
  139. scanf("%s",s + 1);
  140. reverse(s + 1,s + N + 1);
  141. SAM::Init();
  142. for(int i = 1 ; i <= N ; ++i) {
  143. SAM::build_sam(i,s[i] - 'a');
  144. }
  145. SAM::build_suffix_tree();
  146. dfs(1);
  147. for(int j = 1 ; j <= 17 ; ++j) {
  148. for(int i = 1 ; i <= Ncnt ; ++i) {
  149. fa[i][j] = fa[fa[i][j - 1]][j - 1];
  150. }
  151. }
  152. }
  153. void Solve() {
  154. int a,b,c,d;
  155. for(int i = 1 ; i <= M ; ++i) {
  156. read(a);read(b);read(c);read(d);
  157. int l = min(d - c + 1,b - a + 1);
  158. int p = id[c];
  159. for(int j = 17 ; j >= 0 ; --j) {
  160. if(!fa[p][j]) continue;
  161. if(dis[fa[p][j]] >= l) p = fa[p][j];
  162. }
  163. if(Query(rt[p],1,N,a,b - min(dis[p],l) + 1)) {
  164. out(min(dis[p],l));enter;continue;
  165. }
  166. for(int j = 17 ; j >= 0 ; --j) {
  167. if(!fa[p][j]) continue;
  168. int u = fa[p][j];
  169. if(!Query(rt[u],1,N,a,b - dis[u] + 1)) p = fa[p][j];
  170. }
  171. if(dis[p] == dis[fa[p][0]] + 1 || dis[fa[p][0]] >= l) {
  172. p = fa[p][0];
  173. out(min(dis[p],l));enter;
  174. }
  175. else {
  176. int L = dis[fa[p][0]],R = min(dis[p] - 1,l);
  177. while(L < R) {
  178. int mid = (L + R + 1) >> 1;
  179. if(Query(rt[p],1,N,a,b - mid + 1)) L = mid;
  180. else R = mid - 1;
  181. }
  182. out(L);enter;
  183. }
  184. }
  185. }
  186. int main() {
  187. Init();
  188. Solve();
  189. return 0;
  190. }

我的代码能力真是低到要哭了!

真的哭了!

写跪了三次啊,debug到吐啊。。。

【LOJ】#2059. 「TJOI / HEOI2016」字符串的更多相关文章

  1. loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增

    题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...

  2. loj#2054. 「TJOI / HEOI2016」树

    题目链接 loj#2054. 「TJOI / HEOI2016」树 题解 每次标记覆盖整棵字数,子树维护对于标记深度取max dfs序+线段树维护一下 代码 #include<cstdio> ...

  3. 「TJOI / HEOI2016」字符串

    「TJOI / HEOI2016」字符串 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为 \(n\) 的字符串 \(s\),和 ...

  4. loj #2055. 「TJOI / HEOI2016」排序

    #2055. 「TJOI / HEOI2016」排序   题目描述 在 2016 年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他. 这个 ...

  5. LOJ #2058「TJOI / HEOI2016」求和

    不错的推柿子题 LOJ #2058 题意:求$\sum\limits_{i=0}^n\sum\limits_{j=0}^nS(i,j)·2^j·j!$其中$ S(n,m)$是第二类斯特林数 $ Sol ...

  6. loj2059 「TJOI / HEOI2016」字符串

    字符串好难啊不会啊 #include <iostream> #include <cstdio> using namespace std; int n, m, rnk[10000 ...

  7. AC日记——#2057. 「TJOI / HEOI2016」游戏 LOJ

    #2057. 「TJOI / HEOI2016」游戏 思路: 最大流: 代码: #include <cstdio> #include <cstring> #include &l ...

  8. loj2058 「TJOI / HEOI2016」求和 NTT

    loj2058 「TJOI / HEOI2016」求和 NTT 链接 loj 思路 \[S(i,j)=\frac{1}{j!}\sum\limits_{k=0}^{j}(-1)^{k}C_{j}^{k ...

  9. AC日记——#2054. 「TJOI / HEOI2016」树

    #2054. 「TJOI / HEOI2016」树 思路: 线段树: 代码: #include <cstdio> #include <cstring> #include < ...

随机推荐

  1. Docker 基础知识

    Docker Docker 是什么 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是 ...

  2. jquery.lazyload插件实现图片延迟加载详解

    什么是LazyLoad技术? 在页面上图片比较多的时候,打开一张页面必然引起与服务器大数据量的交互.尤其是对于高清晰的图片,占了几百K的空间.Lazy Load 是一个用 JavaScript 编写的 ...

  3. fopen()、fwrite()、fread()函数使用说明与示例

    fopen()函数: 1.作用: 在C语言中fopen()函数用于打开指定路径的文件,获取指向该文件的指针. 2.函数原型: FILE * fopen(const char * path,const  ...

  4. 设置PyCharm中的Python代码模版

    再MacOs运行的PyCharm中,执行python文件,如果不指定python文件字符编码会报错: SyntaxError: Non-ASCII character , but no encodin ...

  5. soj2012.King(有向图+蛋疼得一逼)

    Description There are n children in a country marked by integers from 1 to n. They often fight with ...

  6. java学习第02天(语言基础组成:关键字、标识符、注释、常量和变量)

    Java语言基础组成 1. 关键字 就是指的一些单词,这些单词被赋予了特殊的java含义,就不再叫单词了. 例如: class Demo{ public static void main(String ...

  7. addclass,removeclass

    定义和用法 addClass() 方法向被选元素添加一个或多个类. 该方法不会移除已存在的 class 属性,仅仅添加一个或多个 class 属性. 提示:如需添加多个类,请使用空格分隔类名. rem ...

  8. 【leetcode 简单】 第六十九题 删除链表中的节点

    请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点. 现有一个链表 -- head = [4,5,1,9],它可以表示为: 4 -> 5 -> 1 - ...

  9. SearchSploit

    在我们的GitHub上的Exploit Database存储库中包含一个名为"searchsploit"的Exploit-DB的命令行搜索工具,该工具还允许您在任何地方随身携带一个 ...

  10. 【译】第十一篇 SQL Server代理维护计划

    本篇文章是SQL Server代理系列的第十一篇,详细内容请参考原文 在这一系列的上一篇,我们看了使用代理帐户模仿Windows安全上下文完成作业步骤的工作.大多数子系统支持代理账户,同时子系统限制代 ...