BZOJ4556:[Tjoi2016Heoi2016]字符串 (后缀自动机+树上倍增+二分答案+线段树合并)
题目传送门: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]字符串 (后缀自动机+树上倍增+二分答案+线段树合并)相关推荐
- 【CF700E】Cool Slogans【后缀自动机】【可持久化线段树合并】【树上倍增】
传送门 题意:给定字符串SSS,求一堆字符串s1,s2,s3,...,sks_1,s_2,s_3,...,s_ks1,s2,s3,...,sk,满足s1s_1s1是SSS的子串,且sis_i ...
- 【NOI2018】你的名字【后缀自动机】【可持久化线段树合并】【乱搞】
题意:给一个串 SSS,qqq 次询问,每次给定串 TTT 和 l,rl,rl,r ,求有多少个本质不同的串是 TTT 的子串而不是 Sl-rS_{l\dots r}Sl-r 的子串. ∣S∣≤5× ...
- 牛客多校4 - Ancient Distance(树上倍增+dfs序+线段树)
题目链接:点击查看 题目大意:给出一棵 n 个节点且以点 1 为根节点的的树,现在给出一个 k ,需要在树上选择 k 个关键点,使得 n 个点到达根节点的路径上,出现的最近的关键点的距离的最大值最小, ...
- YbtOJ#463-序列划分【二分答案,线段树,dp】
正题 题目链接:https://www.ybtoj.com.cn/problem/463 题目大意 给出长度为nnn的序列A,BA,BA,B.要求划分成若干段满足 对于任何i<ji<ji& ...
- 【TJOI2016】【bzoj4552】排序(二分答案+线段树01排序)
problem 给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序 排序分为两种 1:(0,l,r)表示将区间[l,r]的数字升序排序 2:(1,l,r)表示将区间[l,r]的数字降序排序 ...
- Bzoj4556 [Tjoi2016Heoi2016]字符串
Time Limit: 20 Sec Memory Limit: 128 MB Submit: 846 Solved: 327 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了 ...
- BZOJ 3277 串 BZOJ 3473 字符串 (广义后缀自动机、时间复杂度分析、启发式合并、线段树合并、主席树)...
标签那么长是因为做法太多了... 题目链接: (bzoj 3277) https://www.lydsy.com/JudgeOnline/problem.php?id=3277 (bzoj 3473) ...
- [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
- Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)
题目链接 \(Description\) 给定一个字符串\(s[1]\).一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\)).求最大的 ...
最新文章
- 【C / C++ 】memset函数
- Hive的基本操作-创建表的格式
- python地图 两点距离_没学过还真不会!怎样才能画出准确的地图?
- Linux下设置和查看环境变量
- 初级使用Latex写论文经验总结
- ftp 根据特定正则匹配文件名 下载到本地 并且上传文件到ftp java *** 最爱那水货...
- cordova插件(github版)
- java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String
- 2.5 结构化程序设计的方法
- unity使用TUIO协议
- 银联支付接口+支付宝接口统一支付功能
- SBI集团“逆市”入股玖富,背后意味着什么?|一点财经
- 微生物组-扩增子16S分析和可视化(2022.10)
- windows cmd 批处理将文件名改为大写:https://blog.csdn.net/llq108/article/details/47185279
- 任正非圣诞发表文章:我在生活所迫时创立华为
- 求int所能表示的最大整数
- html5 js获取鼠标坐标,js怎么获取鼠标在div中的相对位置
- 马士兵Python基础版2020教程P58-P96 PPT笔记+课堂代码
- Cuba 获取当前登录用户
- css 修改文字基准线_HTML4/HTML5 用CSS或style属性修改 hr 实线 虚线 点线 双实线样式 ... ......