后缀数组题目选讲
- Codeforces 1654F Minimal String Xoration
- Luogu P6095 [JSOI2015] 串分割
- P7769 「CGOI-1」大师选徒
- Luogu P7361「JZOI-1」拜神
- Codeforces 822E Liar
- P5284 [十二省联考 2019] 字符串问题
- P5341 [TJOI2019] 甲苯先生和大中锋的字符串
- P6793 [SNOI2020] 字符串
- Luogu P5161 WD与数列
- P4094 [HEOI2016/TJOI2016]字符串
- P5353 树上后缀排序
- SP687 REPEATS - Repeats
- CF1073G Yet Another LCP Problem
- [ICPC2020 Nanjing R] Baby's First Suffix Array Problem
Codeforces 1654F Minimal String Xoration
设 表示 。设 表示 的前 位, 表示 在所有 的排名。
注意到 ,因此可以和 SA 一样倍增排序。
# include <bits/stdc++.h>
const int N=300010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
char s[N];
struct Node{
int fir,sec,id;
bool operator < (const Node &rhs) const{
return (fir!=rhs.fir)?(fir<rhs.fir):(sec<rhs.sec);
}
bool operator != (const Node &rhs) const{
return (*this<rhs)||(rhs<*this);
}
}g[N];
int rk[N];
int n;
int main(void){
n=1<<read();
scanf("%s",s);
for(int i=0;i<n;++i) g[i]=(Node){s[i],0,i};
std::sort(g,g+n);
for(int i=0,cc=0;i<n;++i){
if(i==0||g[i]!=g[i-1]) rk[g[i].id]=++cc;
else rk[g[i].id]=cc;
}
for(int w=1;w<n;w<<=1){
for(int i=0;i<n;++i) g[i]=(Node){rk[i],rk[i^w],i};
std::sort(g,g+n);
for(int i=0,cc=0;i<n;++i){
if(i==0||g[i]!=g[i-1]) rk[g[i].id]=++cc;
else rk[g[i].id]=cc;
}
}
for(int v=0;v<n;++v) if(rk[v]==1){
for(int i=0;i<n;++i) putchar(s[i^v]);
exit(0);
}
return 0;
}
Luogu P6095 [JSOI2015] 串分割
设给定的数串为 ,长度为 ,则答案的位数必然为 。因此,考虑找出所有可能出现的长度为 的子串,并二分检查。
具体地,设当前检查的答案为 ,考虑枚举循环移位的起点,并依次检查。不难想到贪心:如果当前可以匹配 位(接下来 位的字典序不比 大),那么就匹配 位,否则匹配 位。这个策略是可行的,因为如果在可以匹配 位时只匹配了小于 位,则下一次至多也只能匹配 位,这样一定不如直接匹配 位,下一次匹配 位。
最后,还是因为每一次至多只匹配 位,因此循环移位起点在大于 的位置是没有用的(相当于把最前面的一段放到最后去了),不影响检查是否通过。因此我们只枚举起点在 的循环移位,循环内每次检查是 的,因此一次检查是 的。
实现的时候可以直接二分答案在后缀数组上的排名,判断能否匹配 位的依据变为起点在后缀数组中的排名是否不超过当前二分的值。如果我们二分的答案刚好是起点对应后缀的前缀,此时会误判为不能匹配。但这是不影响的,因为起点对应后缀也在后缀数组中,当我们二分到其排名时就能够正常判断了。
# include <bits/stdc++.h>
const int N=400010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,k;
int s[N];
namespace SA{
int sa[N],rk[N],ork[N],buc[N],id[N];
int mx=128;
inline void clear(void){
mx=128;
memset(buc,0,sizeof(buc));
return;
}
inline void init(void){
clear();
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,sizeof(buc));
memcpy(ork,rk,sizeof(ork));
cc=0;
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
for(int i=1;i<=n;++i)
rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
if(cc==n) break;
}
return;
}
}
using namespace SA;
int len;
inline bool chk(int x){
bool ok=false;
for(int i=1;i<=len;++i){
int mat=0;
for(int j=1;j<=k;++j){
int p=(i+mat-1)%(n/2)+1;
if(rk[p]<=x) mat+=len;
else mat+=len-1;
}
ok|=(mat>=(n/2));
}
return ok;
}
int main(void){
n=read(),k=read();
for(int i=1;i<=n;++i) scanf("%1d",&s[i]),s[i+n]=s[i];
len=(n+k-1)/k;
n*=2,init();
int l=1,r=n,ans=n+1;
while(l<=r){
int mid=(l+r)>>1;
if(chk(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
for(int i=1;i<=len;++i) printf("%d",s[sa[ans]+i-1]);
return 0;
}
P7769 「CGOI-1」大师选徒
两个长度为 的序列 关于 满足条件可以转化为:
将 序列的向前差分数组 以及 ( 中元素变为其相反数)拼接到一起,求出其后缀数组。询问时,找到 所在位置 ,二分求出后缀数组上与后缀 LCP 大于等于 的区间。如果这个区间中,有来源于 的位置,并且该位置在 数组上对应的值恰为 ,那么就找到了一组合法解。给每个 开一个 vector,对于后缀数组中每个来源于 的位置,将其位置 push 进对应的 vector 中。查询时二分即可。
注意判掉平凡的无解情况。
# include <bits/stdc++.h>
const int N=800010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,q;
char s[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int ans[N];
int mn[N][20];
int a[N],d[N];
std::vector <int> bc[N];
inline void init_sa(int n){
mx=n+2;
std::fill(buc+1,buc+1+mx,0);
for(int i=1;i<=n;++i) ++buc[rk[i]=d[i]+(::n+1)];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,(mx+1)*4);
memcpy(ork,rk,sizeof(ork));
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
cc=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(d[i+k]==d[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=mn[rk[i]][0]=k;
}
for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i)
mn[i][j]=std::min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
for(int i=1;i<=n;++i){
if(sa[i]>::n) bc[a[sa[i]-::n]].push_back(i);
}
return;
}
inline int lcp(int i,int j){
if(i>j) std::swap(i,j);
if(i==j) return 2*n-sa[i]+1;
int l=std::__lg(j-(i++));
int ans=std::min(mn[i][l],mn[j-(1<<l)+1][l]);
return ans;
}
inline int getpre(int x,int len){
int l=1,r=x,ans=x;
while(l<=r){
int mid=(l+r)>>1;
if(lcp(mid,x)>=len) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
inline int getnex(int x,int len){
int l=x,r=2*n,ans=x;
while(l<=r){
int mid=(l+r)>>1;
if(lcp(mid,x)>=len) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
int main(void){
n=read(),q=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) d[i]=a[i+1]-a[i],d[i+n]=-d[i];
init_sa(2*n);
while(q--){
int s=read(),l=read(),r=read();
int len=r-l,dt=s-a[l];
if(dt<=0||dt>n||!bc[dt].size()){
puts("No");
continue;
}
if(l==r){
puts("Yes");
continue;
}
int L=getpre(rk[l],len),R=getnex(rk[l],len);
auto itl=std::lower_bound(bc[dt].begin(),bc[dt].end(),L),
itr=std::upper_bound(bc[dt].begin(),bc[dt].end(),R);
puts(itl==itr?"No":"Yes");
}
return 0;
}
Luogu P7361「JZOI-1」拜神
类似品酒大会的思路。枚举长度 ,并对于每个 ,维护出位置 左侧最靠右的位置 使得后缀 和后缀 的 LCP 大于等于 。这可以使用并查集 + 启发式合并来实现。具体地,将集合 和 合并时,设我们把所有 插入到 中,那么每插入一个 ,就在原来的 中二分找到 的前驱 和后继 ,并修改 。那么对于长度 而言,只要询问区间 中存在一个大于等于 的数, 就合法。
使用可持久化线段树维护出对于每个 的 数组。查询时二分即可。
# include <bits/stdc++.h>
const int N=50010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,q;
char s[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int ans[N];
inline void init_sa(void){
mx=128;
std::fill(buc+1,buc+1+mx,0);
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,(mx+1)*4);
memcpy(ork,rk,sizeof(ork));
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
cc=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
return;
}
struct Node{
int x,w;
bool operator < (const Node &rhs) const{
return w>rhs.w;
}
};
std::vector <Node> S;
namespace SGT{
struct Node{
int mx,lc,rc;
Node(){mx=INF,lc=rc=0;}
}tr[N*500];
int cnt;
inline int& lc(int x){
return tr[x].lc;
}
inline int& rc(int x){
return tr[x].rc;
}
inline void psup(int k){
return tr[k].mx=std::min(tr[lc(k)].mx,tr[rc(k)].mx),void();
}
void update(int &k,int lst,int l,int r,int x,int v){
k=++cnt,tr[k]=tr[lst];
if(l==r) return tr[k].mx=v,void();
int mid=(l+r)>>1;
if(x<=mid) update(lc(k),lc(lst),l,mid,x,v);
else update(rc(k),rc(lst),mid+1,r,x,v);
psup(k);
return;
}
int query(int k,int l,int r,int L,int R){
if(!k) return INF;
if(L<=l&&r<=R) return tr[k].mx;
int mid=(l+r)>>1,ans=INF;
if(L<=mid) ans=query(lc(k),l,mid,L,R);
if(mid<R) ans=std::min(ans,query(rc(k),mid+1,r,L,R));
return ans;
}
}
std::set <int> st[N];
int f[N];
inline int find(int x){
return (f[x]==x)?x:f[x]=find(f[x]);
}
int rt[N];
inline void merge(int u,int v,int ver){
u=find(u),v=find(v);
if(u==v) return;
if(st[u].size()>st[v].size()) std::swap(u,v);
while(st[u].size()){
int w=*st[u].begin();
auto it=st[v].lower_bound(w);
if(it!=st[v].end()) SGT::update(rt[ver],rt[ver],1,n,w,*it);
if(it!=st[v].begin()) --it,SGT::update(rt[ver],rt[ver],1,n,*it,w);
st[u].erase(w),st[v].insert(w);
}
f[u]=v;
return;
}
int main(void){
n=read(),q=read();
scanf("%s",s+1);
init_sa();
for(int i=2;i<=n;++i) S.emplace_back((Node){i,ht[i]});
std::sort(S.begin(),S.end());
auto it=S.begin();
for(int i=1;i<=n;++i) f[i]=i,st[i].insert(i);
for(int L=n-1;L>=0;--L){
rt[L]=rt[L+1];
while(it!=S.end()&&(*it).w==L){
int p=(*it).x;
merge(sa[p-1],sa[p],L);
++it;
}
}
while(q--){
int L=read(),R=read();
int l=1,r=R-L+1,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(SGT::query(rt[mid],1,n,L,R-mid)<=R-mid+1) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
Codeforces 822E Liar
设 表示只用 的字符,用了 段,最多能匹配到 的哪里。
我们发现,如果我们新开了一段,能匹配就匹配显然是不劣的,于是记 ,则 向 转移。同时也可以不选,于是 也可以向 转移。
# include <bits/stdc++.h>
const int N=200010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,m,lim;
char S[N],T[N];
char s[N];
int len;
int f[N][35];
int ork[N],rk[N],buc[N],sa[N],id[N],mx=128;
int ht[N];
int mn[N][20];
inline void init(void){
for(int i=1;i<=len;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=len;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=len;w<<=1,mx=cc,cc=0){
for(int i=len-w+1;i<=len;++i) id[++cc]=i;
for(int i=1;i<=len;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,sizeof(buc)),memcpy(ork,rk,sizeof(ork)),cc=0;
for(int i=1;i<=len;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=len;i;--i) sa[buc[ork[id[i]]]--]=id[i];
for(int i=1;i<=len;++i)
rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:(++cc));
if(cc==len) break;
}
for(int i=1,k=0;i<=len;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=mn[rk[i]][0]=k;
}
for(int k=1;(1<<k)<=len;++k) for(int i=1;i<=len;++i)
mn[i][k]=std::min(mn[i][k-1],mn[i+(1<<(k-1))][k-1]);
return;
}
inline int lcp(int i,int j){
assert(i!=j),i=rk[i],j=rk[j];
if(i>j) std::swap(i,j);
int k=std::__lg(j-(i++));
return std::min(mn[i][k],mn[j-(1<<k)+1][k]);
}
int main(void){
n=read(),scanf("%s",S+1),m=read(),scanf("%s",T+1),lim=read();
for(int i=1;i<=n;++i) s[++len]=S[i];
s[++len]='@';
for(int i=1;i<=m;++i) s[++len]=T[i];
init();
for(int i=0;i<n;++i) for(int k=0;k<=lim;++k){
f[i+1][k]=std::max(f[i+1][k],f[i][k]);
int lp=lcp(i+1,(n+1)+f[i][k]+1);
f[i+lp][k+1]=std::max(f[i+lp][k+1],f[i][k]+lp);
}
int ans=0;
for(int i=0;i<=lim;++i) ans=std::max(f[n][i],ans);
printf(ans==m?"YES":"NO");
return 0;
}
P5284 [十二省联考 2019] 字符串问题
最后的要求形似一条从 串到 串的路径。
考虑对于一对支配关系 ,哪些 可以通过这对支配关系接到 后面。设 ,那么 必须成立,同时 也要成立。
对于第一个条件,可以二分找到后缀数组上的合法区间,只有 在这个区间中时, 才可能合法。对于第二个条件,似乎没有什么头绪。考虑使用主席树优化建图。具体地,从大到小枚举 ,将 的 插入到 这个位置上,并令虚点连向 的边权值为 。对于所有 的 ,让 和主席树上对应区间连边。主席树上的边权值均为 。
最后,对于每对支配关系 ,将 连边。
答案为图中最长路。不难发现,图上没有零权环,因此有环必为正权环,此时答案为 。使用 Tarjan 判环 即可。
若无环则拓扑排序求出 DAG 上最长路,即可求得答案。
P5341 [TJOI2019] 甲苯先生和大中锋的字符串
我们可以对于每种长度,求出有多少个该长度的子串出现了恰 次。枚举后缀数组上每个区间 ,并钦定子串的 次出现均出现在该区间中,在此条件下,计算有多少子串出现了 次。
事实上,该子串长度必然不会超过该区间后缀的 LCP 长度,否则其出现了小于 次。同时,该子串的长度必然大于 ,否则该子串出现了大于 次。
单调队列维护出区间 LCP 长度(一段 height 的最小值),差分统计答案即可。
# include <bits/stdc++.h>
const int N=100010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,k;
int T;
char s[N];
namespace sa{
int sa[N],rk[N],ork[N],ht[N],buc[N],id[N];
int mx=128;
inline void clear(void){
mx=128;
memset(buc,0,sizeof(buc));
return;
}
inline void init(void){
clear();
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,sizeof(buc));
memcpy(ork,rk,sizeof(ork));
cc=0;
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
for(int i=1;i<=n;++i)
rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
return;
}
int q[N],hd,tl;
inline void ins(int x){
while(hd<=tl&&ht[q[tl]]>=ht[x]) --tl;
q[++tl]=x;
return;
}
inline void chk(int x){ // L moved from x to x+1
while(hd<=tl&&q[hd]<=x+1) ++hd;
return;
}
int d[N];
inline void solve(void){
init(),hd=1,tl=0,ht[n+1]=0;
memset(d,0,sizeof(d));
for(int i=1;i<k;++i) ins(i+1);
for(int i=1;i+k-1<=n;++i){
int r=(k==1)?n-sa[i]+1:ht[q[hd]];
int l=std::max(ht[i],ht[i+k]);
if(l<r) ++d[l+1],--d[r+1];
chk(i),ins(i+k);
}
int mx=0;
for(int i=1;i<=n;++i) d[i]+=d[i-1],(d[i]>=d[mx]?mx=i:mx=mx);
printf("%d\n",(d[mx]==0)?-1:mx);
return;
}
}
int main(void){
T=read();
while(T--){
scanf("%s",s+1),n=strlen(s+1),k=read();
if(n<k){
puts("-1");
continue;
}
sa::solve();
}
return 0;
}
P6793 [SNOI2020] 字符串
现在找出一组 集合中串的配对,考虑如何计算代价。代价即总长度减去每对匹配串的 LCP 长度之和的两倍。
一种直观的想法是品酒大会,倒序枚举 LCP 长度,然后合并后缀数组上的区间,每次匹配两个连通块中还未匹配的串。事实上这就是对的,因为如果现在不匹配以后再匹配的话权值一定更小。
# include <bits/stdc++.h>
const int N=500010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int n,k;
char s[N],A[N],B[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
long long ans;
inline void init_sa(int n){
mx=128;
std::fill(buc+1,buc+1+mx,0);
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,(mx+1)*4);
memcpy(ork,rk,sizeof(ork));
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
cc=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
return;
}
struct Node{
int x,w;
bool operator < (const Node &rhs) const{
return w>rhs.w;
}
};
std::vector <Node> S;
std::set <int> st[N];
int f[N];
inline int find(int x){
return (f[x]==x)?x:f[x]=find(f[x]);
}
int ca[N],cb[N];
inline void merge(int u,int v,int ver){
u=find(u),v=find(v);
if(u==v) return;
f[u]=v;
int l=std::min(ca[u],cb[v]),r=std::min(cb[u],ca[v]);
ans+=1ll*ver*(l+r),ca[u]-=l,cb[v]-=l,cb[u]-=r,ca[v]-=r,ca[v]+=ca[u],cb[v]+=cb[u];
return;
}
int main(void){
n=read(),k=read();
scanf("%s",A+1),scanf("%s",B+1);
for(int i=1;i<=n;++i) s[i]=A[i],s[i+(n+1)]=B[i];
s[n+1]='#',init_sa(2*n+1);
for(int i=2;i<=2*n+1;++i) S.emplace_back((Node){i,ht[i]});
std::sort(S.begin(),S.end());
auto it=S.begin();
for(int i=1;i<=2*n+1;++i) f[i]=i;
for(int i=1;i<=n-k+1;++i) ca[i]=1,cb[i+(n+1)]=1;
for(int L=n;L>=0;--L){
while(it!=S.end()&&(*it).w==L){
int p=(*it).x;
merge(sa[p-1],sa[p],std::min(L,k));
++it;
}
}
printf("%lld",1ll*(n-k+1)*k-ans);
return 0;
}
Luogu P5161 WD与数列
长度为 的贡献有 。考虑长度大于 的串,要求即为差分序列相等。
那么,对于差分序列的两个后缀 ,它们对答案的贡献真的是 吗?很显然并不是,因为子串不能相交或相邻,因此对答案的贡献还要对 取 min。
那么我们可以使用品酒大会的方法(三回啊三回)干掉 的限制。设我们在 的时候合并了后缀集合 和后缀集合 ,若 则交换 ,保证启发式合并的复杂度。枚举计算 计算 和 集合的贡献。对于 集合中的元素 ,如果 ,则贡献为 ,否则为 。据此可以解出四个区间(实际上只需要线段树查询 次),计算每个区间内的元素数量 / 元素和,可以采用线段树合并完成。
# include <bits/stdc++.h>
const int N=300010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
typedef long long ll;
int n,q;
char s[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int d[N];
ll ans;
inline void init_sa(int n){
mx=n;
std::fill(buc+1,buc+1+mx,0);
for(int i=1;i<=n;++i) ++buc[rk[i]=d[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,(mx+1)*4);
memcpy(ork,rk,sizeof(ork));
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
cc=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(d[i+k]==d[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
return;
}
struct Node{
int x,w;
bool operator < (const Node &rhs) const{
return w>rhs.w;
}
};
std::vector <Node> S;
namespace SGT{
struct Node{
int lc,rc,cnt;
ll sum;
Node(){cnt=sum=lc=rc=0;}
}tr[N*21];
int cnt;
inline int& lc(int x){
return tr[x].lc;
}
inline int& rc(int x){
return tr[x].rc;
}
inline void psup(Node &cur,const Node &lc,const Node &rc){
cur.cnt=lc.cnt+rc.cnt,cur.sum=lc.sum+rc.sum;
return;
}
void ins(int &k,int l,int r,int x){
if(!k) k=++cnt;
if(l==r) return tr[k].sum=x,tr[k].cnt=1,void();
int mid=(l+r)>>1;
if(x<=mid) ins(lc(k),l,mid,x);
else ins(rc(k),mid+1,r,x);
psup(tr[k],tr[lc(k)],tr[rc(k)]);
return;
}
void merge(int &cur,int u,int v,int l,int r){
if(!u||!v) return cur=u|v,void();
if(l==r) return tr[cur].sum=tr[u].sum+tr[v].sum,tr[cur].cnt=tr[u].cnt+tr[v].cnt,void();
int mid=(l+r)>>1;
merge(lc(cur),lc(u),lc(v),l,mid);
merge(rc(cur),rc(u),rc(v),mid+1,r);
psup(tr[cur],tr[lc(cur)],tr[rc(cur)]);
return;
}
Node query(int k,int l,int r,int L,int R){
if(L>R||!k) return Node();
if(L<=l&&r<=R) return tr[k];
int mid=(l+r)>>1;
Node res,lh,rh;
if(L<=mid) lh=query(lc(k),l,mid,L,R);
if(mid<R) rh=query(rc(k),mid+1,r,L,R);
psup(res,lh,rh);
return res;
}
void dfs(int k,int l,int r){
if(l==r) return printf("(%d,%lld)",tr[k].cnt,tr[k].sum),void();
int mid=(l+r)>>1;
dfs(lc(k),l,mid),dfs(rc(k),mid+1,r);
return;
}
}
std::vector <int> st[N];
int f[N];
inline int find(int x){
return (f[x]==x)?x:f[x]=find(f[x]);
}
int rt[N];
inline void merge(int u,int v,int ver){
u=find(u),v=find(v);
if(u==v) return;
if(st[u].size()>st[v].size()) std::swap(u,v);
ans+=1ll*st[u].size()*st[v].size()*ver;
for(auto x:st[u]){
int l=std::max(1,x-ver),r=x-1;
SGT::Node res=SGT::query(rt[v],1,n,l,r);
ans-=1ll*(ver+1-x)*res.cnt+res.sum;
l=x+1,r=std::min(x+ver,n);
res=SGT::query(rt[v],1,n,l,r);
ans-=1ll*(ver+1+x)*res.cnt-res.sum;
}
for(auto x:st[u]) st[v].push_back(x);
std::vector <int> ().swap(st[u]);
f[u]=v;
SGT::merge(rt[v],rt[v],rt[u],1,n);
// SGT::dfs(rt[v],1,n);
return;
}
int tp[N];
std::map <int,int> mp;
int main(void){
// freopen("in.txt","r",stdin);
n=read(),ans=1ll*n*(n-1)/2;
for(int i=1;i<=n;++i) tp[i]=read();
for(int i=1;i<n;++i) d[i]=tp[i+1]-tp[i],mp[d[i]]=1;
int mpc=0;
for(auto &cur:mp) cur.second=++mpc;
for(int i=1;i<n;++i) d[i]=mp[d[i]];
init_sa(n-1);
for(int i=2;i<n;++i) S.emplace_back((Node){i,ht[i]});
std::sort(S.begin(),S.end());
auto it=S.begin();
for(int i=1;i<n;++i) f[i]=i,st[i].push_back(i),SGT::ins(rt[i],1,n,i);
for(int L=n-2;L>=0;--L){
while(it!=S.end()&&(*it).w==L){
int p=(*it).x;
merge(sa[p-1],sa[p],L);
++it;
}
}
printf("%lld",ans);
return 0;
}
P4094 [HEOI2016/TJOI2016]字符串
考虑 和 的 LCP 长度,等于 。
其中 是常数,可以最后考虑。重点求 的最大值。
二分答案 。二分找到 的区间,那么这段区间中大于等于 的数才能成为答案。主席树查询即可。
P5353 树上后缀排序
题目等价于这样一件事情:取出节点 到根的字符组成的序列 ,以及根到 的节点编号组成的序列 。比较 时,先比较 的字典序大小,随后比较 的字典序大小。
为了应对关键字 ,我们在倍增排序时引入不可重排名 :在两个串目前相等时不再让它们的排名相等,而直接令 。
现在,考虑在倍增排序时使用三个关键字:靠下半段的可重排名,靠上半段的不可重排名,靠下半段的不可重排名。
第一个是为了区分 靠下半段的字典序,第二个同时区分了 靠上半段的字典序和 靠上半段的字典序。如果比完了这个都仍然相等,那么两个串就相等了,第三关键字的作用就是区分 靠下半段的字典序。
# include <bits/stdc++.h>
const int N=500010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
typedef long long ll;
int n,q;
char s[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx,rrk[N],bk[N];
ll ans;
int f[N][20];
inline void radix_sort(int *res,int *rk,int *id,int mx){
std::fill(buc,buc+1+mx,0);
for(int i=1;i<=n;++i) ++buc[rk[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) res[buc[rk[id[i]]]--]=id[i];
return;
}
inline bool equal(int x,int y,int t){
return ork[x]==ork[y]&&ork[f[x][t]]==ork[f[y][t]];
}
inline void init_sa(int n){
for(int i=1;i<=n;++i) rk[i]=s[i],id[i]=i;
radix_sort(sa,rk,id,128);
for(int i=1,cc=0;i<=n;++i){
rk[sa[i]]=(s[sa[i]]==s[sa[i-1]]?cc:++cc);
rrk[sa[i]]=i;
}
for(int w=1,t=0;w<=n;w<<=1,++t){
for(int i=1;i<=n;++i) bk[i]=rrk[f[i][t]];
radix_sort(ork,bk,sa,n);
radix_sort(sa,rk,ork,n);
std::swap(rk,ork);
for(int i=1,cc=0;i<=n;++i){
rk[sa[i]]=equal(sa[i],sa[i-1],t)?cc:++cc;
rrk[sa[i]]=i;
if(cc==n) return;
}
}
return;
}
int main(void){
n=read();
for(int i=2;i<=n;++i){
f[i][0]=read();
for(int k=1;k<=19;++k) f[i][k]=f[f[i][k-1]][k-1];
}
scanf("%s",s+1);
init_sa(n);
for(int i=1;i<=n;++i) printf("%d ",sa[i]);
return 0;
}
SP687 REPEATS - Repeats
沿用优秀的拆分一题的想法,枚举循环节长度 ,每隔 撒一个关键点。设相邻两个关键点为 。如果确有一个重复子串的相邻两次出现经过 两个关键点,那么 必须成立。反之,如果这条件成立,则我们在原字符串中找到了两个相等且间隔 的子串,它们构成它们并的 border,从而它们的并有周期 。因此 下取整可以作为一个备选答案。
# include <bits/stdc++.h>
const int N=50010,INF=0x3f3f3f3f;
char s[N],fs[N],logt[N];
int n;
struct SuffixArray{
int sa[N],rank[N],len,num,t[N],cnt,fir[N],sec[N],height[N],minx[N][20];
inline void clear(void){
memset(sa,0,sizeof(sa)),memset(fir,0,sizeof(fir)),memset(sec,0,sizeof(sec));
}
inline void build(char *s,int n){
clear();
len=n,num=128;
std::fill(t+1,t+1+num,0);
for(int i=1;i<=n;++i) ++t[fir[i]=s[i]];
for(int i=1;i<=num;++i) t[i]+=t[i-1];
for(int i=n;i;--i) sa[t[fir[i]]--]=i;
for(int k=1;k<=n;k<<=1){
cnt=0;
for(int i=n-k+1;i<=n;++i) sec[++cnt]=i;
for(int i=1;i<=n;++i) if(sa[i]>k) sec[++cnt]=sa[i]-k;
std::fill(t+1,t+1+num,0);
for(int i=1;i<=n;++i) ++t[fir[i]];
for(int i=1;i<=num;++i) t[i]+=t[i-1];
for(int i=n;i;--i) sa[t[fir[sec[i]]]--]=sec[i];
std::swap(sec,fir),cnt=1,fir[sa[1]]=1;
for(int i=2;i<=n;++i)
fir[sa[i]]=((sec[sa[i-1]]==sec[sa[i]]&&sec[sa[i-1]+k]==sec[sa[i]+k])?cnt:++cnt);
if(cnt==n) break;
num=cnt;
}
for(int i=1;i<=n;++i) rank[sa[i]]=i;
return;
}
inline void get_height(char *s){
int n=len;
s[n+1]=s[0]=0;
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rank[i]-1]+k]) ++k;
height[rank[i]]=k;
}
for(int i=1;i<=n;++i) minx[i][0]=height[i];
for(int k=1;(1<<k)<=n;++k){
for(int i=1;i+(1<<k)-1<=n;++i) minx[i][k]=std::min(minx[i][k-1],minx[i+(1<<(k-1))][k-1]);
}
return;
}
inline int query(int l,int r){
int len=logt[r-l+1];
return std::min(minx[l][len],minx[r-(1<<len)+1][len]);
}
inline int querylcp(int l,int r){
if(l==0||r==0||l>n||r>n) return 0;
int a=rank[l],b=rank[r];
if(a>b) std::swap(a,b);
return query(a+1,b);
}
}A,B;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline void solve(void){
for(int i=1;i<=n;++i) fs[i]=s[n-i+1];
A.build(s,n),B.build(fs,n);
A.get_height(s),B.get_height(fs);
int maxx=0;
for(int len=1;len<=n;++len){
for(int L=len,R=2*len;R<=n;L+=len,R+=len){
int lcp=A.querylcp(L,R),lcs=B.querylcp(n-L+2,n-R+2);
maxx=std::max(maxx,(lcp+lcs)/len+1);
}
}
printf("%d\n",maxx);
return;
}
int main(void){
for(int i=2;i<=50000;++i) logt[i]=logt[i>>1]+1;
int T=read();
while(T--){
n=read();
char op[2];
for(int i=1;i<=n;++i) scanf("%s",op),s[i]=op[0];
solve();
}
return 0;
}
CF1073G Yet Another LCP Problem
给定字符集为小写字母的字符串 。
次询问,每次给定大小分别为 的集合 ,求:
将 变为 后排序,将 变成一个递增的序列,放到 数组上进行考虑(原来考虑的是后缀 和后缀 的 ,将 变为 后变为考虑后缀 的 )。因为 数组上有性质 ,所以我们就方便使用数据结构进行整体考虑了。
先假设不存在 。
对于一个 ,我们钦定 只和小于它的 产生贡献。因此,从前往后扫描 ,同时用单调栈维护 。 是我们维护的一个指针,表示我们现在扫到了 数组的第 位。根据 数组的性质,当指针 移动到 时,原有的 变为 ,要对 取 ,对应单调栈的弹出操作。
如果当前的 指向某个 ,则向单调栈添加 这个元素(对应该后缀长度)。如果当前的 指向了 ,那么可以直接在单调栈中查询统计好的答案。
对于 ,我们也可以钦定 只和小于它的 产生贡献。据此可以使用另一个单调栈维护贡献。
注意到,如果存在 ,它们的贡献只能被统计一次。于是我们钦定 只和小于它的 产生贡献, 只和小于等于它的 产生贡献,即可不重不漏地求得答案。
# include <bits/stdc++.h>
const int N=200010,INF=0x3f3f3f3f;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
typedef long long ll;
struct Stack{
int val[N],l[N],top;
ll sum;
inline void init(void){
top=sum=0;
return;
}
inline void check(int cval){
int len=0;
while(top&&val[top]>=cval) sum-=1ll*l[top]*val[top],len+=l[top],--top;
val[++top]=cval,l[top]=len,sum+=1ll*cval*len;
return;
}
inline void push(int cval){
val[++top]=cval,l[top]=1,sum+=cval;
return;
}
}ta,tb;
int a[N],b[N];
int alen,blen;
char s[N];
int n,q;
namespace sa{
int rk[N],sa[N],ork[N];
int buc[N],id[N];
int ht[N];
int mx=128;
int mn[N][20],lg[N];
inline void st_init(void){
for(int i=1;i<=n;++i) mn[i][0]=ht[i],lg[i]=((i==1)?0:lg[i>>1]+1);
for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i)
mn[i][j]=std::min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
return;
}
inline int st_query(int l,int r){
assert(l<=r);
int t=lg[r-l+1];
return std::min(mn[l][t],mn[r-(1<<t)+1][t]);
}
inline int lcp(int l,int r){
if(l>r) std::swap(l,r);
if(l==r) return n-sa[l]+1;
return st_query(l+1,r);
}
inline void init(void){
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;;mx=cc,cc=0,w<<=1){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,sizeof(buc));
memcpy(ork,rk,sizeof(ork));
cc=0;
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i) sa[buc[ork[id[i]]]--]=id[i];
for(int i=1;i<=n;++i)
rk[sa[i]]=((ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc);
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
st_init();
return;
}
inline ll solve(void){
for(int i=1;i<=alen;++i) a[i]=rk[a[i]];
for(int i=1;i<=blen;++i) b[i]=rk[b[i]];
std::sort(a+1,a+1+alen),std::sort(b+1,b+1+blen);
int i=1,j=1,las=0;
ll ans=0;
while(i<=alen||j<=blen){
int op,pos;
if(i>alen||(j<=blen&&b[j]<a[i])) op=2,pos=b[j++];
else op=1,pos=a[i++];
int lcl=lcp(las,pos);
ta.check(lcl),tb.check(lcl);
if(op==1) ans+=tb.sum,ta.push(n-sa[pos]+1);
else ans+=ta.sum,tb.push(n-sa[pos]+1);
las=pos;
}
return ans;
}
}
int main(void){
n=read(),q=read();
scanf("%s",s+1);
sa::init();
while(q--){
alen=read(),blen=read();
for(int i=1;i<=alen;++i) a[i]=read();
for(int i=1;i<=blen;++i) b[i]=read();
printf("%lld\n",sa::solve());
}
return 0;
}
练习题(弱化版本)
- [AHOI2013] 差异
- [Luogu P7409 SvT](P7409 SvT - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
[ICPC2020 Nanjing R] Baby's First Suffix Array Problem
详见题解 https://www.luogu.com.cn/article/iwhoyvnj。
# include <bits/stdc++.h>
const int N=500010,INF=0x3f3f3f3f;
const int MX=500000;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
struct Qdata{
int id,l,r;
};
std::vector <Qdata> Q[N];
std::vector <int> po[N];
int n,m;
char s[N];
int buc[N],sa[N],rk[N],ht[N],id[N],ork[N],mx;
int ans[N];
inline void init_sa(void){
mx=128;
std::fill(buc+1,buc+1+mx,0);
rk[n+1]=0;
for(int i=1;i<=n;++i) ++buc[rk[i]=s[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=1;i<=n;++i) sa[buc[rk[i]]--]=i;
for(int w=1,cc=0;w<=n;w<<=1,mx=cc,cc=0){
for(int i=n-w+1;i<=n;++i) id[++cc]=i;
for(int i=1;i<=n;++i) if(sa[i]>w) id[++cc]=sa[i]-w;
memset(buc,0,(mx+1)*4);
memcpy(ork,rk,sizeof(ork));
for(int i=1;i<=n;++i) ++buc[ork[i]];
for(int i=1;i<=mx;++i) buc[i]+=buc[i-1];
for(int i=n;i;--i)
sa[buc[ork[id[i]]]--]=id[i];
cc=0;
for(int i=1;i<=n;++i)
rk[sa[i]]=(ork[sa[i]]==ork[sa[i-1]]&&ork[sa[i]+w]==ork[sa[i-1]+w])?cc:++cc;
if(cc==n) break;
}
for(int i=1,k=0;i<=n;++i){
if(k) --k;
while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
ht[rk[i]]=k;
}
return;
}
int T1[N],T2[N];
inline int lb(int x){
return x&(-x);
}
inline void add(int *arr,int x,int v){
for(;x<=MX;x+=lb(x)) arr[x]+=v;
return;
}
inline int query(int *arr,int x){
int ans=0;
for(;x;x-=lb(x)) ans+=arr[x];
return ans;
}
int c[N];
int v[N*5],vc;
inline void pl(int x){
v[++vc]=x;
return;
}
inline void recalc(void){
std::sort(v+1,v+1+vc),vc=std::unique(v+1,v+1+vc)-(v+1);
return;
}
inline int d(int x){
int ans=std::lower_bound(v+1,v+1+vc,x)-v;
return assert(v[ans]==x),ans;
}
// id op k x
struct squa{
int id,op,k,x;
};
//id x
struct tri{
int id,x;
};
std::vector <squa> S[N];
std::vector <tri> T[N];
void solve(int L,int R){
if(L==R) return;
int mid=(L+R)>>1;
solve(L,mid),solve(mid+1,R);
c[mid+1]=ht[mid+1];
for(int i=mid+2;i<=R;++i) c[i]=std::min(c[i-1],ht[i]);
int e=n;
vc=0;
for(int i=mid;i>=L;--i){
for(auto C:Q[i]){
int l=C.l,r=C.r;
l=std::max(C.l,sa[i]+1);
if(l<=r){
if(l+e<r) l=r-e;
pl(l),pl(r+1);
}
l=std::max(C.l,std::max(sa[i]+1,r-e+1));
if(l<=r) pl(l),pl(r+1);
}
e=std::min(e,ht[i]);
}
for(int i=mid+1;i<=R;++i) pl(sa[i]);
recalc(),e=n;
for(int i=mid;i>=L;--i){
for(auto C:Q[i]){
int l=C.l,r=C.r,id=C.id;
l=std::max(C.l,sa[i]+1);
if(l<=r){
if(l+e<r) l=r-e;
int dr=d(r+1),dl=d(l);
S[dr].push_back((squa){id,1,-1,e});
S[dl].push_back((squa){id,1,1,e});
T[dl].push_back((tri){id,r});
}
l=std::max(C.l,std::max(sa[i]+1,r-e+1));
if(l<=r){
int dr=d(r+1),dl=d(l);
S[dr].push_back((squa){id,2,-1,e});
S[dl].push_back((squa){id,2,1,e});
}
}
e=std::min(e,ht[i]);
}
for(int i=mid+1;i<=R;++i){
if(c[i]) po[d(sa[i])].push_back(c[i]);
}
for(int i=vc;i;--i){
for(auto p:po[i])
add(T1,p,1),add(T2,p+v[i],1);
for(auto C:S[i]){
int ret=query(T1,C.x);
if(C.op==2) ret=query(T1,n)-ret;
ans[C.id]+=C.k*ret;
}
for(auto C:T[i]){
int ret=query(T2,C.x);
ans[C.id]-=ret;
}
}
for(int i=1;i<=vc;++i){
for(auto p:po[i])
add(T1,p,-1),add(T2,p+v[i],-1);
po[i].clear(),T[i].clear(),S[i].clear();
}
c[mid]=n;
for(int i=mid-1;i>=L;--i) c[i]=std::min(c[i+1],ht[i+1]);
e=n,vc=0;
for(int j=mid+1;j<=R;++j){
for(auto C:Q[j]){
int l=C.l,r=C.r;
l=std::max(C.l,sa[j]+1);
if(l<=r) pl(r+1),pl(l);
l=C.l,r=std::min(C.r,sa[j]-1);
if(l<=r) pl(r+1),pl(l);
}
}
for(int i=L;i<=mid;++i) pl(sa[i]);
recalc();
for(int j=mid+1;j<=R;++j){
e=std::min(e,ht[j]);
for(auto C:Q[j]){
int l=C.l,r=C.r,id=C.id;
l=std::max(C.l,sa[j]+1);
if(l<=r){
int dr=d(r+1),dl=d(l);
S[dr].push_back((squa){id,1,-1,n});
S[dl].push_back((squa){id,1,1,n});
}
l=C.l,r=std::min(C.r,sa[j]-1);
if(l<=r){
int dr=d(r+1),dl=d(l);
int lim=(e<=C.r-sa[j])?n:C.r-sa[j];
S[dr].push_back((squa){id,1,-1,lim});
S[dl].push_back((squa){id,1,1,lim});
}
}
}
for(int i=L;i<=mid;++i){
po[d(sa[i])].push_back(c[i]);
}
for(int i=vc;i;--i){
for(auto p:po[i]) add(T1,p+1,1);
for(auto C:S[i]){
int ret=query(T1,C.x+1);
ans[C.id]+=C.k*ret;
}
}
for(int i=1;i<=vc;++i){
for(auto p:po[i])
add(T1,p+1,-1);
po[i].clear(),T[i].clear(),S[i].clear();
}
return;
}
inline void mian(void){
n=read(),m=read();
scanf("%s",s+1);
init_sa();
for(int i=1;i<=m;++i){
int l=read(),r=read(),k=read();
Q[rk[k]].push_back((Qdata){i,l,r});
}
solve(1,n);
for(int i=1;i<=m;++i) printf("%d\n",ans[i]+1),ans[i]=0;
for(int i=1;i<=n;++i) Q[i].clear();
return;
}
int main(void){
// freopen("in.txt","r",stdin);
int T=read();
while(T--) mian();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现