题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4556


题目分析:我发现我对线段树合并一无所知QAQ。

先讲一种简单的做法:我们可以将后缀数组建出来,对于每个询问二分一个答案mid。然后从Rank[c]往上下两个方向跳,找到一个区间[L,R],使得这个区间的后缀和c开头的后缀的LCP大于等于mid。那么如果sa[L]~sa[R]中有落在[a,b-mid+1]的数,最终答案就大于等于mid。要判断区间[L,R]中是否有属于某个权值区域的数,写个可持久化的权值线段树,然后差分一下就行。

上面的做法代码很短,但常数偏大。其实还可以把反串的后缀自动机建出来,二分完答案mid后,用树上倍增跳到Right(c)-mid所对应的节点,然后看这个节点的Right集中是否包含[b+mid-1,a]中的数(上文的a,b,c均指在反串中的位置)。这可以用线段树合并来实现(当然也可以用DFS序+可持久化线段树,不过我做这题的主要目的是写一写线段树合并)。

为了在省选的赛场上也能码出这种超级数据结构题,我特地测试了一下自己写这题代码的时间。结果我用75min写好了代码,交上去WA了一次,然后用一组手造的小数据检查了一遍,发现有个n+1写成了n,还有线段树合并的时候忘了新建节点qaq,然后再交了一次就……

就不停地RE!!!我不停地debug还是没有发现任何错。到了第二天中午(也就是今天中午),我迫不得已把网上某位dalao的程序copy下来对拍,还是没有拍出任何错QAQ。于是我只好将代码一段一段注释掉,交上BZOJ,看看是哪里RE了。结果我发现是线段树合并出错了。

假设线段树的叶子节点有N个,那么线段树合并的总空间应该是2Nlog(N)2N\log(N)(至少我写的是这样),而我只开了Nlog(N)N\log(N)。这个应该比较好证明:考虑权值线段树上某个代表区间[L,R]的节点,一开始每一个数都要单独开一条链,于是这个位置的节点被新建了R-L+1次。然后区间里的每两个数合并,都要新开一次该位置的节点,所以又要最多开R-L次,于是所有节点加起来就是2Nlog(N)2N\log(N)的空间。

那为什么对拍不出错呢?因为我的字符串是随机生成的,导致建出来的SAM状态数很少。而线段树的叶子节点个数等于SAM的状态数,所以不会爆。我将空间改成2Nlog(N)2N\log(N)之后MLE了,于是我稍微调小了点空间,终于过了。然后我就对着这道题花掉了75min写代码+一个中午debug的时间……


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;const int maxn=100100;
const int maxm=7000000;
const int maxl=20;
const int maxc=26;struct Seg
{int sum;Seg *lson,*rson;
} tree[maxm];
int Tcur=-1;struct Tnode;struct edge
{Tnode *obj;edge *Next;
} e[maxn<<1];
int Ecur=-1;struct Tnode
{int val,Right;edge *head;Seg *Seg_Root;Tnode *son[maxc],*Fa[maxl],*parent;
} SAM[maxn<<1];
Tnode *Last[maxn];
Tnode *Root;
int cur=-1;int a[maxn];
char s[maxn];
int n,m;
int A,b,c,d;Seg *New_Seg()
{Tcur++;tree[Tcur].sum=0;tree[Tcur].lson=tree[Tcur].rson=tree;return tree+Tcur;
}Tnode *New_node(int v,int c)
{cur++;SAM[cur].val=v;SAM[cur].Right=c;SAM[cur].parent=NULL;SAM[cur].head=NULL;SAM[cur].Seg_Root=tree;for (int i=0; i<maxc; i++) SAM[cur].son[i]=NULL;for (int i=0; i<maxl; i++) SAM[cur].Fa[i]=NULL;return SAM+cur;
}void Add(Tnode *x,Tnode *y)
{Ecur++;e[Ecur].obj=y;e[Ecur].Next=x->head;x->head=e+Ecur;
}void Update(Seg *&root,int L,int R,int x)
{if (root==tree) root=New_Seg();if (L==R) root->sum=1;else{int mid=(L+R)>>1;if (x<=mid) Update(root->lson,L,mid,x);else Update(root->rson,mid+1,R,x);root->sum=root->lson->sum+root->rson->sum;}
}void Merge(Seg *&x,Seg *y)
{if (y==tree) return;if (x==tree){x=y;return;}Seg *z=x;x=New_Seg();(*x)=(*z); //要记得新开节点!!!Merge(x->lson,y->lson);Merge(x->rson,y->rson);x->sum=x->lson->sum+x->rson->sum;
}void Dfs(Tnode *node)
{Update(node->Seg_Root,1,n+1,node->Right);for (edge *p=node->head; p; p=p->Next){Tnode *to=p->obj;Dfs(to);Merge(node->Seg_Root,to->Seg_Root);
//我这里的线段树合并要两倍SAM节点数*log(n)的空间,也就是maxn*maxl<<2(注意被卡空间)!!!}
}void Build_SAM()
{Root=New_node(0,n+1);Last[0]=Root;for (int i=1; i<=n; i++){Tnode *P=Last[i-1],*NP=New_node(i,i);Last[i]=NP;int to=a[i];while ( P && !P->son[to] ) P->son[to]=NP,P=P->parent;if (!P) NP->parent=Root;else{Tnode *Q=P->son[to];if (P->val+1==Q->val) NP->parent=Q;else{Tnode *NQ=New_node(P->val+1,n+1);for (int i=0; i<maxc; i++) NQ->son[i]=Q->son[i];NQ->parent=Q->parent;NP->parent=Q->parent=NQ;while ( P && P->son[to]==Q ) P->son[to]=NQ,P=P->parent;}}}for (int i=0; i<=cur; i++){Tnode *P=SAM+i;P->Fa[0]=P->parent;}for (int j=1; j<maxl; j++)for (int i=0; i<=cur; i++){Tnode *P=SAM+i;if (P->Fa[j-1]) P->Fa[j]=P->Fa[j-1]->Fa[j-1];}for (int i=1; i<=cur; i++){Tnode *P=SAM+i;Add(P->parent,P);}Dfs(Root);
}Tnode *Jump(int x,int y)
{Tnode *P=Last[x];for (int j=maxl-1; j>=0; j--)if ( P->Fa[j] && P->Fa[j]->val>=y ) P=P->Fa[j];return P;
}bool Query(Seg *root,int L,int R,int x,int y)
{if ( y<L || R<x || root==tree ) return false;if ( x<=L && R<=y ) return root->sum;int mid=(L+R)>>1;bool fL=Query(root->lson,L,mid,x,y);bool fR=Query(root->rson,mid+1,R,x,y);return ( fL || fR );
}bool Judge(int len)
{Tnode *node=Jump(c,len);return Query(node->Seg_Root,1,n+1,b+len-1,A);
}int Binary()
{int L=0,R=min(A-b+1,c-d+1)+1;while (L+1<R){int mid=(L+R)>>1;if ( Judge(mid) ) L=mid;else R=mid;}return L;
}int main()
{freopen("4556.in","r",stdin);freopen("4556.out","w",stdout);scanf("%d%d",&n,&m);scanf("%s",&s);for (int i=1; i<=n; i++) a[i]=s[i-1]-'a';for (int i=1; i<=(n>>1); i++) swap(a[i],a[n-i+1]);New_Seg();Build_SAM();for (int i=1; i<=m; i++){scanf("%d%d%d%d",&A,&b,&c,&d);A=n-A+1;b=n-b+1;c=n-c+1;d=n-d+1;int ans=Binary();printf("%d\n",ans);}return 0;
}

BZOJ4556:[Tjoi2016Heoi2016]字符串 (后缀自动机+树上倍增+二分答案+线段树合并)相关推荐

  1. 【CF700E】Cool Slogans【后缀自动机】【可持久化线段树合并】【树上倍增】

    传送门 题意:给定字符串SSS,求一堆字符串s1,s2,s3,...,sks_1,s_2,s_3,...,s_ks1​,s2​,s3​,...,sk​,满足s1s_1s1​是SSS的子串,且sis_i ...

  2. 【NOI2018】你的名字【后缀自动机】【可持久化线段树合并】【乱搞】

    题意:给一个串 SSS,qqq 次询问,每次给定串 TTT 和 l,rl,rl,r ,求有多少个本质不同的串是 TTT 的子串而不是 Sl-rS_{l\dots r}Sl-r​ 的子串. ∣S∣≤5× ...

  3. 牛客多校4 - Ancient Distance(树上倍增+dfs序+线段树)

    题目链接:点击查看 题目大意:给出一棵 n 个节点且以点 1 为根节点的的树,现在给出一个 k ,需要在树上选择 k 个关键点,使得 n 个点到达根节点的路径上,出现的最近的关键点的距离的最大值最小, ...

  4. YbtOJ#463-序列划分【二分答案,线段树,dp】

    正题 题目链接:https://www.ybtoj.com.cn/problem/463 题目大意 给出长度为nnn的序列A,BA,BA,B.要求划分成若干段满足 对于任何i<ji<ji& ...

  5. 【TJOI2016】【bzoj4552】排序(二分答案+线段树01排序)

    problem 给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序 排序分为两种 1:(0,l,r)表示将区间[l,r]的数字升序排序 2:(1,l,r)表示将区间[l,r]的数字降序排序 ...

  6. Bzoj4556 [Tjoi2016Heoi2016]字符串

    Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 846  Solved: 327 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了 ...

  7. BZOJ 3277 串 BZOJ 3473 字符串 (广义后缀自动机、时间复杂度分析、启发式合并、线段树合并、主席树)...

    标签那么长是因为做法太多了... 题目链接: (bzoj 3277) https://www.lydsy.com/JudgeOnline/problem.php?id=3277 (bzoj 3473) ...

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

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

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

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

  10. Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)

    题目链接 \(Description\) 给定一个字符串\(s[1]\).一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\)).求最大的 ...

最新文章

  1. 【C / C++ 】memset函数
  2. Hive的基本操作-创建表的格式
  3. python地图 两点距离_没学过还真不会!怎样才能画出准确的地图?
  4. Linux下设置和查看环境变量
  5. 初级使用Latex写论文经验总结
  6. ftp 根据特定正则匹配文件名 下载到本地 并且上传文件到ftp java *** 最爱那水货...
  7. cordova插件(github版)
  8. java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String
  9. 2.5 结构化程序设计的方法
  10. unity使用TUIO协议
  11. 银联支付接口+支付宝接口统一支付功能
  12. SBI集团“逆市”入股玖富,背后意味着什么?|一点财经
  13. 微生物组-扩增子16S分析和可视化(2022.10)
  14. windows cmd 批处理将文件名改为大写:https://blog.csdn.net/llq108/article/details/47185279
  15. 任正非圣诞发表文章:我在生活所迫时创立华为
  16. 求int所能表示的最大整数
  17. html5 js获取鼠标坐标,js怎么获取鼠标在div中的相对位置
  18. 马士兵Python基础版2020教程P58-P96 PPT笔记+课堂代码
  19. Cuba 获取当前登录用户
  20. css 修改文字基准线_HTML4/HTML5 用CSS或style属性修改 hr 实线 虚线 点线 双实线样式 ... ......

热门文章

  1. JS的this指向问题(史上最全)
  2. 【winRAR去广告弹窗】
  3. 安全渗透测试 服务器 系统,一次完整的安全渗透测试
  4. 南阳oj 括号配对问题
  5. This computer does not support Intel Virtualization Technology (VT-x) or it is being exclusively use
  6. 服务器英文系统怎么切中文,云服务器怎么把英文改成中文
  7. 五个最佳媒体格式转换器
  8. 【工具】SMART原则的分析举例注意事项
  9. 各种笔记本进入BIOS的快捷键
  10. 汉王手写芯片的触摸屏控制器应用设计