P4482 [BJWC2018] Border 的四种求法
基本子串结构做法。
题传
推荐阅读
对正反串分别建立 SAM(以下简称正反 SAM)。
我们尝试为每个本质不同子串一种标记方式,对反 SAM 的 parent 树重链剖分(若有子树相同 size 优先选择最后一次出现位置更晚的那个),对于反串 SAM 上的每个结点
通过这样的编号方式,结合基本子串结构相关知识,可以导出以下结论:
-
对于反串 SAM 的一个结点,其代表的所有等价类在
xOy 平面上为一条竖线段。 -
对于正串 SAM 的一个结点,其代表的所有等价类在
xOy 平面上为一条斜线段。 -
对于反串 SAM 的任意一条由上至下的链,其代表的所有等价类在
xOy 平面上仍为一条竖线段。
考虑同时对正 SAM parent 树做树链剖分,区间 border 即为
将询问挂在正串 SAM 的重链上,离线后枚举每条重链,问题相当于加入
重新设置坐标,横坐标不变,纵坐标减去原来的横坐标,那么加入的斜线段变成横线段,询问仍然是竖线段。对新的横坐标扫描线,线段树查询区间最靠右不为 1 点即可。复杂度
Code:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <cctype>
#include <vector>
#include <queue>
#include <bitset>
#include <assert.h>
#define mcy(arra, arrb) memcpy(arra, arrb, sizeof(arra))
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
char ch=getchar();int x=0, f=1;
while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
inline void write(int x){
if(x<0) putchar('-'), x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
int ret=1;
for(; b; b>>=1, a=1ll*a*a%cp)
if(b&1) ret=1ll*ret*a%cp;
return ret;
}
const int N=2e5+5;
const int M=N<<1;
struct SAM{
int tr[M][26], len[M], fa[M][21], ndc, lst;
inline int clr(int x){len[x]=fa[x][0]=0;mcy(tr[x], tr[0]);return x;}
inline int rmk(){return lst=clr(ndc=1);}
inline int insert(char cc){
int p=lst, cur=clr(++ndc), c=cc-'a';len[cur]=len[p]+1;
for(; p&&!tr[p][c]; p=fa[p][0]) tr[p][c]=cur;int q=tr[p][c];
if(!q) fa[cur][0]=1;else if(len[p]+1==len[q]) fa[cur][0]=q;
else{
int nex=clr(++ndc);len[nex]=len[p]+1, fa[nex][0]=fa[q][0], mcy(tr[nex], tr[q]);
for(; p&&tr[p][c]==q; p=fa[p][0]) tr[p][c]=nex;fa[q][0]=fa[cur][0]=nex;
}
return lst=cur;
}
vi G[M];int mxp[M], pos[N], siz[M], son[M], top[M], edr[M];
void dfs(int x){
for(int i=1; i<=20; ++i) fa[x][i]=fa[fa[x][i-1]][i-1];int y;siz[x]=1;
for(auto v:G[x]) dfs(v), siz[x]+=siz[v], mxp[x]=max(mxp[x], mxp[v]),
y=son[x], son[x]=(siz[y]==siz[v])?(mxp[y]<mxp[v]?v:y):(siz[y]<siz[v]?v:y);
}
int id[M], cnt, bac[M], L, R;
void dfs(int x, int rt){
top[x]=rt;if(son[x]) dfs(son[x], rt);edr[x]=son[x]?edr[son[x]]:mxp[x];
for(auto v:G[x]) if(!top[v]) bac[id[v]=++cnt]=v, dfs(v, v),
L=min(L, len[x]+1-edr[v]), R=max(R, len[v]-edr[v]);
}
void build(char *str, int len){
L=INF;rmk();for(int i=1; i<=len; ++i) mxp[pos[i]=insert(str[i])]=i;
for(int i=2; i<=ndc; ++i) G[fa[i][0]].pb(i);dfs(1), son[1]=0;dfs(1, 1);
}
inline int mnl(int x){return len[fa[x][0]]+1;}
inline int find(int x, int y){
int u=pos[y], k=y-x+1;for(int i=20; i>=0; --i)
if(len[fa[u][i]]>=k) u=fa[u][i];return u;
}
}A, B;
namespace SegmentTree{
int L, R, mx[M<<2];
void init(int l, int r){L=l, R=r;}
#define ls k<<1
#define rs k<<1|1
#define mid (l+r>>1)
void modify(int k, int l, int r, int x, int v){
if(l==r) return (void)(mx[k]+=v);
if(x<=mid) modify(ls, l, mid, x, v);
else modify(rs, mid+1, r, x, v);
mx[k]=max(mx[ls], mx[rs]);
}
void update(int p, int v){modify(1, 1, R-L+1, p-L+1, v);}
int query(int k, int l, int r, int x, int y){
if(!mx[k]||x>r||y<l) return -INF;if(l==r) return mid+L-1;
int t=query(rs, mid+1, r, x, y);return (t!=-INF)?t:query(ls, l, mid, x, y);
}
int qry(int l, int r){return query(1, 1, R-L+1, l-L+1, r-L+1);}
#undef ls
#undef rs
#undef mid
}
char s[N];int n, m, ans[N];
struct node{int qid, rid, riq, len;};vector <node> Q[M];
inline void wrk(int id, int l, int r){
int u=A.find(l, r), v=B.find(n-r+1, n-l+1);
for(u=A.fa[u][0], v=B.fa[v][0]; u!=1&&v!=1; ){
int fu=A.top[u], a=A.mnl(fu), b=A.len[u];
int fv=B.top[v], c=B.mnl(fv), d=B.len[v];
Q[A.id[fu]].pb((node){id, fv, v, min(r-l, A.len[u])});
if(a<c) v=B.fa[fv][0];else u=A.fa[fu][0];
//[a, b] [c, d] [a, c b d]
}
}
struct seg{int d, x, y, op, qid;};
inline void slv(int lid){
vector <seg> vec;
for(int x=A.bac[lid]; x; x=A.son[x]){
// printf("-->%d ", x);
int mnl=A.mnl(x), r=A.mxp[x], l=r-mnl+1, u=B.find(n-r+1, n-l+1);
int linknxt=B.edr[u], k=mnl-linknxt;//[x=linknxt, y=len[x]-k]
assert(linknxt<=A.len[x]-k);
vec.pb((seg){linknxt-1, k, k, -2});vec.pb((seg){A.len[x]-k, k, k, 0});
}
for(auto v:Q[lid]){
int x=B.edr[v.rid], l=B.mnl(v.rid), r=min(B.len[v.riq], v.len);
if(l<=r) vec.pb((seg){x, l-x, r-x, v.qid});\
}
sort(vec.begin(), vec.end(), [&](seg a, seg b){if(a.d==b.d) return a.op<b.op;return a.d>b.d;});
for(auto v:vec){
if(v.op<=0) SegmentTree :: update(v.x, v.op+1);
else ans[v.op]=max(ans[v.op], v.d+SegmentTree :: qry(v.x, v.y));
}
}
signed main(){
scanf("%s", s+1);n=strlen(s+1);
A.build(s, n);reverse(s+1, s+n+1);B.build(s, n);m=read();
for(int i=1, l, r; i<=m; ++i) l=read(), r=read(), wrk(i, l, r);
SegmentTree :: init(-n-4, n+4);for(int i=1; i<=A.cnt; ++i) slv(i);
for(int i=1; i<=m; ++i) printf("%d\n", ans[i]);
return 0;
}
一些实现细节:
我实际上写的重剖是拿在原串中最早出现的重儿子,是本质相同的。
当询问的是重链前缀时,需要对长度进行额外限制。