4556: [Tjoi2016&Heoi2016]字符串

字符串题不会做先想能不能把字符串反过来

把字符串反序,建立后缀自动机,利用线段树合并算出每个位置的right集

二分答案,用树上倍增找到对应的节点,看是否有[a+mid-1,b]中的数在right集中

  
  
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
 
#define ll long long
#define inf 1e9
#define eps 1e-10
#define md
#define N 200010
using namespace std;
int ch[N][26],fail[N],c[N],q[N],fa[N][20],len[N],root[N],pos[N];
int son[8000010][2];
char st[N];
int tnt,cnt=1,last=1,n;
 
void sig_ins(int &i,int l,int r,int x)
{
i=++tnt;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) sig_ins(son[i][0],l,mid,x);
else sig_ins(son[i][1],mid+1,r,x);
}
 
int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
int z=++tnt;
son[z][0]=merge(son[x][0],son[y][0]);
son[z][1]=merge(son[x][1],son[y][1]);
return z;
}
 
bool find(int x,int l,int r,int ql,int qr)
{
if (!x) return 0;
if (ql<=l&&r<=qr) return 1;
int mid=(l+r)>>1;
if (ql<=mid&&find(son[x][0],l,mid,ql,qr)) return 1;
if (mid+1<=qr&&find(son[x][1],mid+1,r,ql,qr)) return 1;
return 0;
}
int insert(int c,int id)
{
int p=last,x=last=++cnt; len[x]=len[p]+1;
sig_ins(root[x],1,n,id);
while (p&&!ch[p][c]) ch[p][c]=x,p=fail[p];
if (!p) fail[x]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fail[x]=q;
else
{
int cpy=++cnt; len[cpy]=len[p]+1;
memcpy(ch[cpy],ch[q],sizeof(ch[q]));
fail[cpy]=fail[q]; fail[x]=fail[q]=cpy;
while (p&&ch[p][c]==q) ch[p][c]=cpy,p=fail[p];
}
}
return x;
}
 
void get_fail()
{
for (int i=1;i<=cnt;i++) c[len[i]]++;
for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
for (int i=cnt;i;i--) q[c[len[i]]--]=i;
for (int i=cnt;i;i--)
{
int x=q[i],f=fail[x];
root[f]=merge(root[f],root[x]);
}
for (int i=1;i<=cnt;i++) fa[i][0]=fail[i];
for (int j=1;j<=18;j++)
for (int i=1;i<=cnt;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
 
bool ok(int mid,int x,int L,int R)
{
L=L+mid-1; if (L>R) return 0;
for (int j=18;j>=0;j--)
if (len[fa[x][j]]>=mid) x=fa[x][j];
return find(root[x],1,n,L,R);
}
int main()
{
//printf("%d\n",(sizeof(fa)+sizeof(ch)+sizeof(c)*6+sizeof(son))/1024/1024);
int m; scanf("%d%d",&n,&m);
scanf("%s",st+1); reverse(st+1,st+n+1);
for (int i=1;i<=n;i++) pos[i]=insert(st[i]-'a',i);
get_fail();
for (int i=1;i<=m;i++)
{
int a,b,c,d;
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; swap(a,b); swap(c,d);
int l=0,r=d-c+1;
while (l!=r)
{
int mid=(l+r+1)>>1;
if (ok(mid,pos[d],a,b)) l=mid; else r=mid-1;
}
printf("%d\n",l);
}
return 0;
}
/*
字符串从1开始
把字符串反序,询问[l,r]相当于[n-r+1,n-l+1]
建出后缀自动机,对fail树建好倍增数组,用可持久化的方式进行线段树合并
二分答案len l=0 r= d-c+1
找到点满足len>=mid,然后查询是否存在[a,b-len+1]是否存在
找存在的最大值
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值