「雅礼集训 2017 Day7」事情的相似度
求区间 [ l , r ] [l,r] [l,r]内两个前缀 s [ 1.. a ] , s [ 1... b ] ( a , b ∈ [ l , r ] , a ≠ b ) s[1..a],s[1...b](a,b\in [l,r],a\neq b) s[1..a],s[1...b](a,b∈[l,r],a=b)的最长公共后缀长度的最大值。
后缀自动机,用 LCT \texttt{LCT} LCT维护 fail \texttt{fail} fail树。
离线,从前到后每次加入一个前缀,将这个前缀在后缀自动机上的点 a c c e s s access access。
发现这是一个每次将树上一个点到根的路径染色的形式。
a c c e s s access access时维护每个点被最后一次染色的时间。
当新的一次 a c c e s s access access抵达一个点 x x x时,这个 x x x的最后一次染色和这次染色可以看做这两个前缀的 l c a lca lca到根的路径包含 x x x。
直接对于最后一次染色的时间在树状数组上对 x x x的字符串长度取 max \max max。
处理到前缀 r r r时在树状数组上回答所有 [ l , r ] [l,r] [l,r]的询问即可。
A C C o d e \mathcal AC \ Code AC Code
#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,m;
namespace BIT{
int tr[maxn];
void upd(int u,int v){
for(;u;u-=u&-u) tr[u]=max(tr[u],v); }
int qry(int u){
int r=0;for(;u<=n;u+=u&-u) r=max(r,tr[u]);return r; }
}
char s[maxn];
int fa[maxn],ch[maxn][2],tr[maxn][2],tg[maxn],pos[maxn],len[maxn],tot=1,last=1;
void ins(int c){
// rooted 1
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;p && !tr[p][c];p=fa[p]) tr[p][c]=u;
if(!p) fa[u] = 1;
else if(len[q = tr[p][c]] == len[p] + 1) fa[u] = q;
else{
int v = ++tot;
tr[v][0] = tr[q][0],
tr[v][1] = tr[q][1],
len[v] = len[p] + 1,
fa[v] = fa[q];
for(;p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[u] = fa[q] = v;
}
}
#define pa fa[x]
int inr(int x){
return ch[pa][1] == x; }
int isr(int x){
return ch[pa][0] ^ x && ch[pa][1] ^ x; }
void dt(int x){
if(ch[x][0]) tg[ch[x][0]] = tg[x];
if(ch[x][1]) tg[ch[x][1]] = tg[x];
}
void dtpath(int x){
if(!isr(x)) dtpath(pa);dt(x); }
void rot(int x){
int y = fa[x] , z = fa[y] , c = inr(x);
if(!isr(y)) ch[z][inr(y)] = x;
(ch[y][c] = ch[x][!c]) && (fa[ch[y][c]] = y);
fa[fa[ch[x][!c] = y] = x] = z;
}
void splay(int x){
for(dtpath(x);!isr(x);rot(x))
if(!isr(pa))
rot(inr(pa) ^ inr(x) ? x : pa);
}
int access(int x,int id){
int y = 0;
for(;x;x=fa[y=x]){
splay(x);
if(tg[x])
BIT::upd(tg[x] , len[x]);
ch[x][1] = y;
tg[x] = id;
}
return y;
}
vector<int>G[maxn];
int l[maxn],r[maxn],ans[maxn];
int main(){
scanf("%d%d",&n,&m);
scanf("%s",s+1);
rep(i,1,n) ins(s[i]-'0'),pos[i] = last;
rep(i,1,m) scanf("%d%d",&l[i],&r[i]),G[r[i]].push_back(i);
rep(i,1,n){
access(pos[i],i);
rep(j,0,G[i].size()-1){
int v = G[i][j];
ans[v] = BIT::qry(l[v]);
}
}
rep(i,1,m) printf("%d\n",ans[i]);
}
「十二省联考 2019」字符串问题
给定一个字符串,给出 n a n_a na个点,每个点是字符串中的一个子串 S [ l . . . r ] S[l...r] S[l...r],每个点有若干类边,每类边可以被表示为该点连向 包含字符串某一个子串 S [ a . . . b ] S[a...b] S[a...b]作为前缀 的点,求最长路或判断没有最长路。
发现作为前缀这个条件,可以简单的对反串建后缀自动机,则连上 f a i l \rm fail fail树中父亲到儿子的有向边即可表示作为前缀这个条件。
但是有一个细节上的问题就是后缀自动机的点是多个不同的串,在后缀自动机上是同一个点的串有时需要连边有时又不需要连边,这个问题很好解决,就对于每个点存一下所有串,按长度排个序再连边即可。
注意后缀自动机上串定位可以用倍增实现。
A C C o d e \mathcal AC \ Code AC Code
#include<bits/stdc++.h>
#define maxn 800005
#define maxc 26
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lim 19
using namespace std;
int n;
char S[maxn];
int fa[maxn],f[lim][maxn],tr[maxn][maxc],len[maxn],pos[maxn],tot,last;
void ins(int c){
int u = ++tot , p = last , q;
len[last = u] = len[p] + 1;
for(;~p && tr[p][c]==0;p=fa[p]) tr[p][c] = u;
if(p==-1) fa[u] = 0;
else if(len[q=tr[p][c]] == len[p] + 1) fa[u] = q;
else{
int v = ++tot;
memcpy(tr[v],tr[q],sizeof tr[q]);
fa[v] = fa[q] , len[v] = len[p] + 1;
for(;~p && tr[p][c] == q;p=fa[p]) tr[p][c] = v;
fa[q] = fa[u] = v;
}
}
vector<int>G[maxn];int Le[maxn],na,nb;
LL dis[maxn];
int info[maxn],Prev[maxn<<2],to[maxn<<2],cst[maxn<<2],IN[maxn],cnt_e,cnt_p;
void Node(int u,int v,int c=0){
Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c,IN[v]++; }
int in[maxn],ot[maxn];
bool cmp(const int &u,const int &v){
return Le[u] == Le[v] ? u > v : Le[u] < Le[v]; }
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
int T;
fa[0] = -1;
for(scanf("%d",&T);T--;){
scanf("%s",S+1);
n = strlen(S+1);
reverse(S+1,S+n+1);
rep(i,1,n) ins(S[i]-'a'),pos[i]=last;
rep(i,1,tot) f[0][i] = fa[i];
rep(j,1,lim-1) rep(i,1,tot) f[j][i] = f[j-1][f[j-1][i]];
scanf("%d",&na);
rep(i,1,na){
int l,r;
scanf("%d%d",&l,&r);
swap(l,r);
l = n - l + 1 , r = n - r + 1;
Le[i] = r - l + 1;
int u = pos[r];
per(j,lim-1,0) if(len[f[j][u]] >= Le[i])
u = f[j][u];
G[u].push_back(i);
}
scanf("%d",&nb);
rep(i,1,nb){
int l,r;
scanf("%d%d",&l,&r);
swap(l,r);
l = n - l + 1 , r = n - r + 1;
Le[i+na] = r - l + 1;
int u = pos[r];
per(j,lim-1,0) if(len[f[j][u]] >= Le[i+na])
u = f[j][u];
G[u].push_back(i+na);
}
cnt_p = na + nb;
rep(i,1,tot){
sort(G[i].begin(),G[i].end(),cmp);
in[i] = ++cnt_p;
int p = in[i];
rep(j,0,G[i].size()-1){
if(G[i][j] <= na)
Node(p,G[i]