[BJWC2018]Border 的四种求法

description

luogu
给一个小写字母字符串\(S\),\(q\)次询问每次给出\(l,r\),求\(s[l..r]\)的\(Border\)。

solution

我们考虑转化题面:给定\(l,r\),求满足\(lcs(i,r)\ge i-l+1\)的最大的\(i\)。
对于\(lcs(i,r)\),考虑对\(S\)构建\(SAM\),那么我们知道\(lcs\)的可能取值就是从后缀\(r\)所代表的节点沿着\(fail\)树到根节点的路径上节点的\(len\)的取值。
那么我们得到了一个暴力做法:\(SAM\)+线段树合并后暴跳\(fail\)树,
在每个节点的线段树中查询\([l,min(r,len+l-1))\)中最大的\(i\)。

如何优化?
考虑树链剖分,我们要求出的一条根到父亲的链在\(fail\)树上是若干段重链的前缀。
那么可以对每条重链进行离线而分开考虑。
查询前缀时,除了最后一个点需要查询重儿子及其子树,其余的点都只要查询轻儿子及其子树即可。
因此查询前缀时对于最后一个点仍然使用\(SAM\)+线段树合并的做法,
对于其他的点,考虑将\(lcs(i,r)\ge i-l+1\)变为\(i-len[x]+1\le l\)
只须从上往下一个个插入轻子树的儿子+回答询问。
回答询问时,需要建立一棵以\(i\)为下标,权值为\(i-len[x]+1\)的最小值线段树,
查询时需要查询\(l\le i<r\)且\(i-len[x]+1\le l\)的最大的\(i\),
此时线段树上二分,根据右子树的最小值是否\(\le l\)作出决策,单次时间复杂度降为\(O(logn)\)。
由于每个点到根节点的轻边仅\(O(log)\)条,因此插入的总时间复杂度为\(O(nlog^2n)\)。
线段树处理询问的总时间同样为\(O(nlog^2n)\)。
故总时间复杂度为\(O(nlog^2n)\)。

code

话说这是我第一次写SAM+线段树合并
我错了是我人丑常数大1e5还要跑300ms

#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define FL "a"
using namespace std;
typedef unsigned long long ll;
typedef pair<int,int> PI;
typedef vector<int> VI;
typedef long double dd;
const int N=4e5+10;
const int mod=1e9+7;
const int inf=2147483647;
inline ll read(){
  ll data=0,w=1;char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
  if(ch=='-')w=-1,ch=getchar();
  while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
  return data*w;
}
inline void file(){
  freopen(FL".in","r",stdin);
  freopen(FL".out","w",stdout);
}
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
struct node{int l,r,id;};
int n,q;char s[N];vector<node>Q[N];int ans[N];
int head[N],nxt[N],to[N],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int lst=1,tot=1,cnts=0,len[N],fa[N],tr[N][26],pos[N],fid[N];
inline void extend(int c){
  int cur=++tot,u=lst;len[cur]=len[lst]+1;
  while(u&&!tr[u][c])tr[u][c]=cur,u=fa[u];
  if(!u)fa[cur]=1;
  else{
    int v=tr[u][c];
    if(len[v]==len[u]+1)fa[cur]=v;
    else{
      int clone=++tot;len[clone]=len[u]+1;
      memcpy(tr[clone],tr[v],sizeof(tr[clone]));
      fa[clone]=fa[v];fa[v]=fa[cur]=clone;
      while(u&&tr[u][c]==v)tr[u][c]=clone,u=fa[u];
    }
  }
  lst=cur;pos[lst]=++cnts;fid[cnts]=lst;
}
namespace s1{
  int rt[N],s[2][20*N],tot=0;
  void Insert(int &i,int l,int r,int p){
    i=++tot;s[0][i]=s[1][i]=0;if(l==r)return;
    p<=mid?Insert(s[0][i],l,mid,p):Insert(s[1][i],mid+1,r,p);
  }
  int Merge(int a,int b){
    if(!a||!b)return a|b;int c=++tot;
    s[0][c]=Merge(s[0][a],s[0][b]);
    s[1][c]=Merge(s[1][a],s[1][b]);
    return c;
  }
  int Query(int i,int l,int r,int x,int y){
    if(!i)return 0;if(l==r)return l;
    if(mid<y&&s[1][i]){
      int res=Query(s[1][i],mid+1,r,x,y);
      return res?res:Query(s[0][i],l,mid,x,y);
    }
    else return x<=mid?Query(s[0][i],l,mid,x,y):0;
  }
  inline void insert(int u){Insert(rt[u],1,n,pos[u]);}
  inline void merge(int u,int v){rt[u]=Merge(rt[u],rt[v]);}
  inline int query(int u,int l,int r){return Query(rt[u],1,n,l,r);}
  void print(int i,int l,int r){
    if(!i)return;if(l==r){printf("%d ",l);return;}
    print(s[0][i],l,mid);print(s[1][i],mid+1,r);
  }
}
namespace s2{
  int mn[N<<2],tot;
  void build(int i,int l,int r){
    mn[i]=inf;if(l==r)return;build(ls,l,mid);build(rs,mid+1,r);
  }
  void del(int i,int l,int r,int p){
    mn[i]=inf;if(l==r)return;
    p<=mid?del(ls,l,mid,p):del(rs,mid+1,r,p);
  }
  void insert(int i,int l,int r,int p,int v){
    if(p<l||p>r)return;mn[i]=min(mn[i],v);if(l==r)return;
    p<=mid?insert(ls,l,mid,p,v):insert(rs,mid+1,r,p,v);
  }
  int query(int i,int l,int r,int x,int y,int v){
    if(mn[i]>v)return 0;if(l==r)return mn[i]<=v?l:0;
    if(mid<y&&mn[rs]<=v){
      int res=query(rs,mid+1,r,x,y,v);
      return res?res:query(ls,l,mid,x,y,v);
    }
    else return x<=mid?query(ls,l,mid,x,y,v):0;
  }
}
int sz[N],dep[N],son[N],top[N];
void dfs1(int u){
  sz[u]=1;dep[u]=dep[fa[u]]+1;son[u]=0;
  for(int i=head[u],v;i;i=nxt[i]){
    dfs1(v=to[i]);sz[u]+=sz[v];
    if(sz[son[u]]<sz[v])son[u]=v;
  }
}
void dfs2(int u,int tp){
  top[u]=tp;if(pos[u])s1::insert(u);
  if(son[u])dfs2(son[u],tp),s1::merge(u,son[u]);
  for(int i=head[u],v;i;i=nxt[i])
    if((v=to[i])!=son[u])dfs2(v,v),s1::merge(u,v);
}
void ins(int u,int len){
  if(pos[u])s2::insert(1,1,n,pos[u],pos[u]-len+1);
  for(int i=head[u],v;i;i=nxt[i])ins(v=to[i],len);
}
void del(int u){
  if(pos[u])s2::del(1,1,n,pos[u]);
  for(int i=head[u],v;i;i=nxt[i])del(v=to[i]);
}
inline void getans(int x){
  vector<int>a;a.clear();int m=0;
  for(;x;x=son[x])a.pb(x),m++;
  for(int k=0,u;k<m;k++){
    u=a[k];
    if(pos[u])s2::insert(1,1,n,pos[u],pos[u]-len[u]+1);
    for(int i=head[u],v;i;i=nxt[i]){
      v=to[i];if(v==son[u])continue;ins(v,len[u]);
    }
    for(int i=0,sz=Q[u].size(),l,r,id,res;i<sz;i++){
      l=Q[u][i].l;r=Q[u][i].r;id=Q[u][i].id;
      res=s2::query(1,1,n,l,r-1,l);
      if(l<=res&&res<r)ans[id]=max(ans[id],res-l+1);
      res=s1::query(u,l,min(len[u]+l-1,r-1));
      if(l<=res&&res<r)ans[id]=max(ans[id],res-l+1);
    }
  }
  for(int k=0,u;k<m;k++){
    u=a[k];if(pos[u])s2::del(1,1,n,pos[u]);
    for(int i=head[u],v;i;i=nxt[i]){
      v=to[i];if(v==son[u])continue;del(v);
    }
  }
  for(int k=0,u;k<m;k++){
    u=a[k];
    for(int i=head[u],v;i;i=nxt[i]){
      v=to[i];if(v==son[u])continue;
      getans(v);
    }
  }
}
inline void init(){
  scanf("%s",s+1);n=strlen(s+1);
  for(int i=1;i<=n;i++)extend(s[i]-'a');
  for(int i=2;i<=tot;i++)add(fa[i],i);
  dfs1(1);dfs2(1,1);s2::build(1,1,n);
}
inline void solve(){
  q=read();
  for(int i=1,l,r,u;i<=q;i++){
    l=read();r=read();u=fid[r];
    while(u)Q[u].pb((node){l,r,i}),u=fa[top[u]];
  }
  getans(1);
  for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
}
int main()
{
  init();
  solve();
  return 0;
}

原文地址:https://www.cnblogs.com/cjfdf/p/10260758.html

时间: 2024-10-08 23:44:11

[BJWC2018]Border 的四种求法的相关文章

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

题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度. 题解 这题的描述很短,给人一种很可做的假象. 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq. 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在pa

斐波那契的四种求法

首先看一下斐波那契的矩阵表示: 数列的递推公式为:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2)(n>=3)  用矩阵表示为: 进一步,可以得出直接推导公式: #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> #include<queue> #define N 1000 using

CSS的四种引入方式

CSS的引入方式最常用的有三种, 第一:在head部分加入<link  rel="stylesheet" type="text/css" href="my.css"/>,引入外部的CSS文件. 这种方法可以说是现在占统治地位的引入方法.如同IE与浏览器.这也是最能体现CSS特点的方法:最能体现DIV+CSS中的内容与显示分离的思想,也最易改版维护,代码看起来也是最美观的一种. 第二:在head部分加入<style type=&q

CSS四种设置方式

上面这张思维导图已经大概的讲明白了四种设置方式的不同点,下面就细入说明一下各自的用法和注意点. 1.嵌入样式表 <html> <head> <title>CSS四种设置方式</title> </head> <body> <p style="color:red;font-size:2cm;background-color:gray; border:2px solid blue">内联样式表</p&g

四种常见的提示弹出框(success,warning,error,loading)原生JavaScript和jQuery分别实现

虽然说现在官方的自带插件已经有很多了,但是有时候往往不能满足我们的需求,下面我简单介绍一些 常见的四种提示弹出框(success,loading,error,warning),我分别用原生JavaScript和jQuery来介绍分享给各位博友! 一.首先介绍原生JavaScript来实现四种提示弹出框: 第一步:先看看html的建立 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:

Tomcat反向代理+负载均衡的四种方法配置

环境拓扑 环境说明: IP地址 功能描述 nginx or httpd 172.16.4.100 代理服务器,将用户请求分发到后端的Tomcat服务器实现反向代理和负载均衡 Tomcat-01 172.16.4.101 后端实际提供服务的服务器 Tomcat-02 172.16.4.102 后端实际提供服务的服务器 系统环境都如下所示: [[email protected] ~]# cat /etc/redhat-release CentOS release 6.6 (Final) [[emai

清除浮动——让包围元素包含浮动元素的四种方法

什么是CSS清除浮动? 在非IE浏览器(如Firefox)下,当容器的高度为auto,且容器的内容中有浮动(float为left或right)的元素,在这种情况下,容器的高度不能自动伸长以适应内容的高度,使得内容溢出到容器外面而影响(甚至破坏)布局的现象.这个现象叫浮动溢出,为了防止这个现象的出现而进行的CSS处理,就叫CSS清除浮动. 有一段代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta

iOS设置圆角的四种方法

一.设置CALayer的cornerRadius cornerRadius属性影响layer显示的background颜色和前景框border,对layer的contents不起作用.故一个imgView(类型为UIImageView)的image不为空,设置imgView.layer的cornerRadius,是看不出显示圆角效果的,因为image是imgView.layer的contents部分. 这种情况下将layer的masksToBounds属性设置为YES,可以正确的绘制出圆角效果.

CSS样式四种引入方式

第一种将样式代码写在页面<style>......</style>标签之中,用<style>......</style>标签将body设置一个灰色的背景: <!DOCTYPE html> <html>  <head>   <meta charset="utf-8">   <title></title>   <style>    body{     back