字氟串做题记录

P5284 [十二省联考 2019] 字符串问题

解法

考虑使用 SAM 对应 S 的每个子串。在将某个区间 [l,r] 的子串对应到结点上时,可以先预处理出 [l,|S|] 后缀对应的节点,然后在 link 树上倍增到 [l,r] 区间。

考虑题目中“最长目标串”,可以将支配关系和前缀关系用有向图的形式表现出来,每个子串向支配的子串连边,同时从每个子串的真前缀向对应子串连边(等效于从某个子串 s长度为 |s|1 的前缀向 s 连边)。由于第二种边构成了一棵内向树,所以图中出现了环则一定至少经过了一条第一种边(至少多了一个子串),此时答案为 1;否则答案就是最长路。

对应在 SAM 上,则第二种边可能是某个节点内的子串的边,也可能是某个节点向对应 link 连的边;至于减少前者的数量,可以把第一种边没有连接到的子串缩为一条边(或者是只有在连第一种边时才进行拆点),此时点数和边数就在 O(m+|S|) 级别内了。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxl=20;
const int maxn=200010;
const int maxd=maxn*2;
const int maxv=maxn*4;
const int maxe=maxn*6;
int t,n,m,i,j,p,q,l,r,c;
int td,te,gt,tp,tot,lst,cur;
int ut[maxn],vt[maxn],wt[maxn];
int d[maxv],len[maxd],lnk[maxd];
int fa[maxl][maxd],nxt[maxd][26];
int he[maxd],ne[maxd],le[maxd],pf[maxn];
int pe[maxd],pd[maxd],sd[maxd],ed[maxd];
int gh[maxv],gn[maxe],gv[maxe],ge[maxe];
long long ds,dis[maxv]; char s[maxn]; queue<int> Q;
inline bool cmp(int x,int y){return le[x]<le[y];}
inline void Add(int x,int y,int e){
gv[++gt]=y; ge[gt]=e;
gn[gt]=gh[x]; gh[x]=gt;
}
int main(){
scanf("%d",&t);
memset(dis,-1,sizeof(dis));
while(t--){
gt=td=te=0; tot=lst=1;
memset(nxt[1],0,104);
scanf("%s",s+1);
n=strlen(s+1);
reverse(s+1,s+n+1);
for(i=1;i<=n;++i){
c=s[i]-'a';
cur=++tot; len[cur]=i;
memset(nxt[cur],0,104);
for(p=lst;p;p=lnk[p]){
if(nxt[p][c]) break;
else nxt[p][c]=cur;
}
if(p){
q=nxt[p][c];
if(len[q]==len[p]+1) lnk[cur]=q;
else{
r=++tot;
len[r]=len[p]+1;
lnk[r]=lnk[q];
lnk[q]=lnk[cur]=r;
memcpy(nxt[r],nxt[q],104);
while(nxt[p][c]==q){
nxt[p][c]=r;
p=lnk[p];
}
}
}
else lnk[cur]=1; lst=cur;
}
for(i=2;i<=tot;++i) fa[0][i]=lnk[i];
for(j=1;j<maxl;++j)
for(i=2;i<=tot;++i)
fa[j][i]=fa[j-1][fa[j-1][i]];
for(i=p=1;i<=n;++i) pf[i]=(p=nxt[p][s[i]-'a']);
scanf("%d",&te);
for(i=1;i<=te;++i){
scanf("%d%d",&l,&r);
l=n-l+1; r=n-r+1; swap(l,r);
p=pf[r]; r=r-l+1;
for(j=maxl-1;j>=0;--j)
if(len[fa[j][p]]>=r)
p=fa[j][p];
le[i]=r; ne[i]=he[p]; he[p]=i;
}
scanf("%d",&m);
for(i=1;i<=m;++i){
scanf("%d%d",&l,&r);
l=n-l+1; r=n-r+1; swap(l,r);
p=pf[r]; r=r-l+1;
for(j=maxl-1;j>=0;--j)
if(len[fa[j][p]]>=r)
p=fa[j][p];
le[++te]=r; ne[te]=he[p]; he[p]=te;
}
n=te-m; scanf("%d",&m);
for(i=1;i<=m;++i){
scanf("%d%d",&p,&q);
ut[i]=p; vt[i]=n+q;
wt[i]=le[p];
}
for(i=2;i<=tot;++i){
if(!he[i]) continue;
for(j=he[i],tp=0;j;j=ne[j]) pe[++tp]=j;
sort(pe+1,pe+tp+1,cmp);
p=pe[1]; r=le[p];
pd[p]=sd[i]=++td;
for(j=2;j<=tp;++j){
p=le[pe[j]];
if(p!=r){
Add(td,td+1,0);
++td; r=p;
}
pd[pe[j]]=td;
}
ed[i]=td;
}
for(i=1;i<=m;++i)
Add(pd[ut[i]],pd[vt[i]],wt[i]);
for(i=2;i<=tot;++i){
p=lnk[i];
if(!sd[p]) sd[p]=ed[p]=++td;
if(!sd[i]) sd[i]=ed[i]=++td;
Add(ed[p],sd[i],0);
}
for(i=1,++td;i<=n;++i) Add(pd[i],td,le[i]);
j=dis[r=sd[1]]=0;
for(i=1;i<=gt;++i) ++d[gv[i]];
for(Q.push(r);!Q.empty();){
p=Q.front(); Q.pop();
ds=dis[p]; ++j;
for(i=gh[p];i;i=gn[i]){
q=gv[i];
dis[q]=max(dis[q],ds+ge[i]);
if(!(--d[q])) Q.push(q);
}
}
if(j!=td) dis[td]=-1;
printf("%lld\n",dis[td]);
memset(d+1,0,td<<2);
memset(gh+1,0,td<<2);
memset(he+1,0,tot<<2);
memset(sd+1,0,tot<<2);
memset(ed+1,0,tot<<2);
memset(dis+1,-1,td<<3);
}
return 0;
}

LibreOJ6041 事情的相似度

解法

From grass8sheep.

考虑问题求的是结尾在一段区间内的前缀的两两 LCS 的最大长度,将整个序列翻转则答案为结尾在一段区间内的后缀的两两 LCP 的最大长度。对于某个询问,可以先求 height 数组,字典序相邻的后缀的 LCP 才可能为答案,问题变成了求若干相邻区间内 height 区间 minmax。而删去某个后缀时,如果删去的是排在开头/结尾的后缀则直接删去对应区间的贡献;否则需要删去两端区间 minmax,总之需要将某个数删除,然后求留下的数的 max。可以用桶维护每个值的出现次数,同时维护当前所有存在的值的链表,如果某个数出现次数为 0 则将这个数在链表内删去,达成 O(1) 删除的效果。可以使用回滚莫队解决该问题,最优时间复杂度为 O(nm)(初始化时如果不使用计数排序会多一个 log,但是使用 std::sort 还是可以通过的,因为 std::sort 常数不大,开 O2 一秒能跑 1e7)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int S=320;
const int maxl=19;
const int maxn=100010;
int hd,pd,cv,cp,tp;
int n,m,i,j,k,t,u,v,l,r,L,R,B=-1;
int tiv[maxn],tip[maxn],st[maxl][maxn];
int sa[maxn],rk[maxn],nk[maxn],ht[maxn];
int pt[maxn],nt[maxn],pv[maxn],nv[maxn];
int c[maxn],pe[maxn],cd[maxn],ans[maxn];
char s[maxn];
inline void cmax(int &x,int y){if(x<y) x=y;}
inline int Que(int l,int r){
int le=__lg(r-l+1);
return min(st[le][l],
st[le][r-(1<<le)+1]);
}
struct query{
int l,r,lb,id;
inline bool operator <(const query &a)const{
if(lb!=a.lb) return lb<a.lb;
return r>a.r;
}
}Q[maxn];
struct change{int pre,nxt,val;}V[maxn],P[maxn];
inline void MemV(int p){
if(tiv[p]==i) return;
V[++cv]={pt[p],nt[p],p};
tiv[p]=i; cd[p]=c[p];
}
inline void MemP(int p){
if(tip[p]==i) return;
P[++cp]={pv[p],nv[p],p}; tip[p]=i;
}
inline void DelP(int p,bool f){
int v=0; if(pv[p]) v=Que(pv[p],p-1);
if(nv[p]) cmax(v,Que(p,nv[p]-1)); ++v;
if(f){
MemV(pt[v]); MemV(nt[v]); MemV(v);
MemP(pv[p]); MemP(nv[p]);
}
if(!(--c[v])){
nt[pt[v]]=nt[v];
pt[nt[v]]=pt[v];
if(hd==v) hd=pt[v];
}
nv[pv[p]]=nv[p];
pv[nv[p]]=pv[p];
}
int main(){
scanf("%d%d%s",&n,&m,s+1);
reverse(s+1,s+n+1);
for(i=1;i<=n;++i) ++c[rk[i]=s[i]];
c[u='1']=n;
for(i=n;i;--i) sa[c[rk[i]]--]=i;
c['0']=c['1']=0;
for(j=1;j<=n;j<<=1){
for(i=n-j+1;i<=n;++i) nk[++t]=i;
for(i=1;i<=n;++i) if(sa[i]>j) nk[++t]=sa[i]-j;
for(i=1;i<=n;++i) ++c[rk[i]];
for(i=2;i<=u;++i) c[i]+=c[i-1];
for(i=n;i;--i) sa[c[rk[nk[i]]]--]=nk[i];
memset(nk+1,0,n<<2);
swap(rk,nk); rk[sa[1]]=t=1;
for(i=2;i<=n;++i){
if(nk[sa[i]]!=nk[sa[i-1]]||
nk[sa[i]+j]!=nk[sa[i-1]+j]) ++t;
rk[sa[i]]=t;
}
if(t==n) break;
memset(c+1,0,u<<2);
u=t; t=0;
}
for(i=1,j=0;i<=n;++i){
if(rk[i]==1) continue; if(j) --j;
for(u=sa[rk[i]-1];i+j<=n&&u+j<=n&&s[i+j]==s[u+j];++j);
ht[rk[i]-1]=j;
}
memcpy(st[0]+1,ht+1,n<<2);
for(j=1;j<maxl;++j){
r=n-(1<<j)+1;
for(i=1;i<=r;++i)
st[j][i]=min(st[j-1][i],
st[j-1][i+(1<<(j-1))]);
}
for(i=1,t=0;i<=m;++i){
scanf("%d%d",&l,&r);
l=n-l+1; r=n-r+1; swap(l,r);
if(l/S==r/S){
tp=hd=0;
for(j=l;j<=r;++j) pe[++tp]=rk[j]; sort(pe+1,pe+tp+1);
for(j=1;j<tp;++j) cmax(hd,Que(pe[j],pe[j+1]-1));
ans[i]=hd;
}
else Q[++t]={l,r,l/S,i};
}
sort(Q+1,Q+t+1);
for(i=1;i<=t;++i){
if(B!=Q[i].lb){
B=Q[i].lb;
L=max(1,B*S);
R=Q[i].r; tp=0;
for(j=L;j<=R;++j) pe[++tp]=rk[j];
memset(pt+1,0,n<<2);
memset(nt+1,0,n<<2);
memset(pv+1,0,n<<2);
memset(nv+1,0,n<<2);
memset(c,0,(n+1)<<2);
sort(pe+1,pe+tp+1);
for(j=1;j<tp;++j){
v=pe[j];
nv[v]=pe[j+1];
pv[v]=pe[j-1];
}
pv[pe[tp]]=pe[tp-1];
for(j=1;j<tp;++j){
v=Que(pe[j],pe[j+1]-1)+1;
pe[j]=v; ++c[v];
}
sort(pe+1,pe+tp);
tp=unique(pe+1,pe+tp)-pe-1;
hd=pe[tp];
for(j=1;j<tp;++j){
v=pe[j];
pt[v]=pe[j-1];
nt[v]=pe[j+1];
}
pt[pe[tp]]=pe[tp-1];
}
for(r=Q[i].r;R>r;--R) DelP(rk[R],0);
pd=hd;
for(l=L;l<Q[i].l;++l) DelP(rk[l],1);
ans[Q[i].id]=hd-1;
hd=pd;
while(cv){
u=V[cv].val;
pt[u]=V[cv].pre;
nt[u]=V[cv--].nxt;
c[u]=cd[u];
}
while(cp){
u=P[cp].val;
pv[u]=P[cp].pre;
nv[u]=P[cp--].nxt;
}
}
for(i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}

CF700E Cool Slogans

解法

显然只需要讨论每个 si1si 后缀的情况。如果 si1 不是 si 的后缀,则 si 可以截短到 si1 刚好出现第二次的位置,显然还满足 si+1 内至少出现了两次 si 的限制。

考虑把问题搬到后缀自动机上,则每个 si 对应的节点一定是 si+1 的节点在 lnk 树上的祖先。考虑能否将每个 si 取到对应节点的最长串上方便计算。

考虑某个节点 u 和其某个祖先 v,如果 longest(v) 没有在 longest(u) 中出现两次,而 v 对应的某个串 slongest(u) 内出现了两次,则每个 longest(u) 所在的位置一定存在某个串 S 满足 longest(v)S 内出现了两次,且 |S|>len(u);而 endpos(u)endpos(S),两者互相矛盾。综上,一定可以把每个 si 取到对应节点的 longest

考虑在 lnk 树上 dp。设 dpilongest(i)sk 时的最大 k,如果 i 有某个祖先节点 j,满足对于某个 uendpos(j)vendpos(i),v[ulen(j)+len(i),u1],则 dpj 可以转移到 dpi。(此时如果将 dpi 设为 longest(i)s1 的最大 k,则需要将子树内信息合并,不好处理)维护每个节点的 endpos 可以使用可持久化线段树合并的方式统计。显然从根节点到某个节点的路径上的 dp 值递增,所以在计算某个 dpi 时,只需要判断 dp 值为 dpfai 的最浅祖先节点能否转移过来即可

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=60;
const int maxn=200010;
const int maxd=maxn<<1;
int n,i,p,q,c,u,te,lp,rp,mp,cur,lst=1,tot=1,ans=1;
int len[maxd],lnk[maxd],nxt[maxd][26];
int pos[maxd],rt[maxd],ct[maxn],rk[maxd];
int ls[maxd*maxl],rs[maxd*maxl];
int dp[maxd],hd[maxd]; char s[maxn];
int Merge(int x,int y,int l,int r){
if(!(x&&y)) return x|y;
int m=(l+r)>>1,rt=++te;
if(l!=r){
ls[rt]=Merge(ls[x],ls[y],l,m);
rs[rt]=Merge(rs[x],rs[y],m+1,r);
}
return rt;
}
bool Query(int x,int l,int r,int L,int R){
if(!x) return 0;
if(L<=l&&R>=r) return 1;
int m=(l+r)>>1;
return (L<=m&&Query(ls[x],l,m,L,R))||
(R>m&&Query(rs[x],m+1,r,L,R));
}
int main(){
scanf("%d%s",&n,s+1);
for(i=1;i<=n;++i){
c=s[i]-'a'; cur=++tot;
len[cur]=pos[cur]=i;
for(p=lst;p;p=lnk[p]){
if(nxt[p][c]) break;
else nxt[p][c]=cur;
}
if(q=nxt[p][c]){
if(len[q]==len[p]+1) lnk[cur]=q;
else{
u=++tot;
len[u]=len[p]+1;
lnk[u]=lnk[q];
lnk[q]=lnk[cur]=u;
memcpy(nxt[u],nxt[q],104);
pos[u]=pos[q];
while(nxt[p][c]==q){
nxt[p][c]=u;
p=lnk[p];
}
}
}
else lnk[cur]=1; lst=cur;
lp=1; rp=n; p=rt[cur]=++te;
while(lp<rp){
mp=(lp+rp)>>1; ++te;
if(i<=mp){rp=mp;ls[p]=te;}
else{lp=mp+1;rs[p]=te;} p=te;
}
}
for(i=1;i<=tot;++i) ++ct[len[i]];
for(i=1;i<=n;++i) ct[i]+=ct[i-1];
for(i=1;i<=tot;++i) rk[ct[len[i]]--]=i;
for(i=tot-1;i;--i){
p=rk[i+1]; q=lnk[p];
rt[q]=Merge(rt[p],rt[q],1,n);
}
hd[1]=1;
for(i=2;i<=tot;++i){
u=rk[i]; p=lnk[u];
if(p==1) dp[u]=1,hd[u]=u;
else{
p=hd[p]; q=pos[u];
if(Query(rt[p],1,n,q-len[u]+len[p],q-1))
hd[u]=u,dp[u]=dp[p]+1;
else{
p=lnk[u];
hd[u]=hd[p];
dp[u]=dp[p];
}
if(dp[u]>ans) ans=dp[u];
}
}
printf("%d",ans);
return 0;
}

P6816 [PA2009]Quasi-template

解法

首先考虑处理每个子串对应的 endpos,则某个子串 s 对应的 endpos(s) 内如果有一对相邻的数 x,y 满足 yx>|s|,则 s 无法覆盖 [x,y] 内的所有区域。所以对于某个 endpos 集合,候选的 s 的最小长度为内部相邻元素的差值的最大值。

n=|S|L=miniendpos(s)i,R=maxiendpos(s)i,考虑如何使用 s 覆盖 [1,L)(R,n] 的区域。

覆盖 [1,L) 时,需要有某个位置 p 满足 px1S1ps 的后缀。设 则 pnextL 时,由 L 的定义有 |s|>p(所以此时的 p 最优),故此时需要 L|s|+1nextL+1|s|LnextL

在覆盖 (R,n] 时,需要有某个位置 p 满足 pR+1Spns 的前缀。考虑翻转 S,设 next 为反串的 next,则 np+1 需要取 nextnR+|s|,同理可得 nR+1nextnR+|s|+1nextnR+|s|nR。由于之前的 |s| 被限制在了某个区间内,所以查询满足要求的 |s| 的数量和最小值等效于区间内查询大于某个数的数的数量和最小下标,可以使用主席树解决。

至于最后查询字典序最小的 s,可以哈希 + 二分解决。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=20;
const int maxn=200010;
const int maxd=maxn*2;
const int maxs=maxd*maxl*2;
int n,i,j,p,q,l,r,c,a=maxn,t;
int te,tn,lp,rp,mp,lst=1,cur,tot=1;
int nxt[maxd][26],ct[maxn];
int n1[maxn],n2[maxn],rn[maxn];
int pe[maxd],ls[maxs],rs[maxs];
int rt[maxd],len[maxd],lnk[maxd];
char s[maxn]; uint64_t sm,hs[maxn],pw[maxn];
struct seg{
int lc,rc,dt;
inline seg operator +(const seg &a)const{
return (seg){lc,a.rc,max(max(dt,a.dt),a.lc-rc)};
}
}t1[maxs];
struct seg2{int ls,rs,cnt;}t2[maxn*maxl];
#define lc(p) t1[p].lc
#define rc(p) t1[p].rc
#define dt(p) t1[p].dt
#define ls(p) t2[p].ls
#define rs(p) t2[p].rs
#define cnt(p) t2[p].cnt
int Merge(int x,int y,int l,int r){
if(!(x&&y)) return x|y;
int p=++te,m=(l+r)>>1;
ls[p]=Merge(ls[x],ls[y],l,m);
rs[p]=Merge(rs[x],rs[y],m+1,r);
if(ls[p]&&rs[p]) t1[p]=t1[ls[p]]+t1[rs[p]];
else t1[p]=t1[ls[p]|rs[p]]; return p;
}
void quesum(int x,int l,int r){
if(!cnt(x)) return;
if(p<=l&&q>=r){
cur+=cnt(x);
if(!lp){
while(l<r){
mp=(l+r)>>1;
if(ls(x)) x=ls(x),r=mp;
else x=rs(x),l=mp+1;
}
lp=r-j;
}
return;
}
int m=(l+r)>>1;
if(p<=m) quesum(ls(x),l,m);
if(q>m) quesum(rs(x),m+1,r);
}
int main(){
scanf("%s",s+1);
n=strlen(s+1); pw[0]=1;
for(i=2,j=0;i<=n;++i){
while(j&&s[j+1]!=s[i]) j=n1[j];
n1[i]=(j+=(s[j+1]==s[i]));
}
for(i=1;i<=n;++i){
c=s[i]-'a';
cur=++tot; len[cur]=i;
for(p=lst;p;p=lnk[p]){
if(nxt[p][c]) break;
else nxt[p][c]=cur;
}
if(q=nxt[p][c]){
if(len[q]==len[p]+1) lnk[cur]=q;
else{
r=++tot;
len[r]=len[p]+1;
lnk[r]=lnk[q];
lnk[q]=lnk[cur]=r;
memcpy(nxt[r],nxt[q],104);
while(nxt[p][c]==q){
nxt[p][c]=r;
p=lnk[p];
}
}
}
else lnk[cur]=1;
lst=cur; lp=1; rp=n;
p=rt[cur]=++te;
lc(p)=rc(p)=i;
while(lp<rp){
mp=(lp+rp)>>1; ++te;
if(i<=mp) ls[p]=te,rp=mp;
else rs[p]=te,lp=mp+1;
p=te; lc(p)=rc(p)=i;
}
pw[i]=pw[i-1]*1033;
hs[i]=hs[i-1]+pw[i]*(c+'a');
}
reverse(s+1,s+n+1);
for(i=2,j=0;i<=n;++i){
while(j&&s[j+1]!=s[i]) j=n2[j];
n2[i]=(j+=(s[j+1]==s[i]));
}
reverse(s+1,s+n+1);
for(i=1;i<=n;++i) ++ct[n2[i]];
for(i=1;i<n;++i) ct[i]+=ct[i-1];
for(i=n;i;--i) pe[ct[n2[i]]--]=i;
reverse(pe+1,pe+n+1); q=n2[pe[1]];
for(i=1,r=0;i<=n;++i){
p=pe[i]; ++tn;
while(q>n2[p]) rn[--q]=tn;
rn[q]=tn;
t2[tn]=t2[r];
lp=1; rp=n;
r=l=tn; ++cnt(tn);
while(lp<rp){
mp=(lp+rp)>>1; ++tn;
if(p<=mp){
t2[tn]=t2[ls(l)];
ls(l)=tn; rp=mp;
}
else{
t2[tn]=t2[rs(l)];
rs(l)=tn; lp=mp+1;
}
++cnt(tn); l=tn;
}
}
memset(ct,0,n<<2);
for(i=tot;i;--i) ++ct[len[i]];
for(i=1;i<=n;++i) ct[i]+=ct[i-1];
for(i=tot;i;--i) pe[ct[len[i]]--]=i;
for(i=tot;i;--i){
p=lnk[q=pe[i]];
rt[p]=Merge(rt[p],rt[q],1,n);
}
for(i=2;i<=tot;++i){
p=lc(r=rt[i]); j=n-rc(r); q=len[i]+j;
p=max(max(len[lnk[i]]+1,dt(r)),p-n1[p])+j;
if(p>q) continue; cur=lp=0; quesum(rn[j],1,n);
if(cur){
sm+=cur; if(lp<a) a=lp,t=0;
if(lp==a) pe[++t]=rc(rt[i])-lp+1;
}
}
printf("%llu\n",sm);
p=pe[1];
for(i=2;i<=t;++i){
r=q=pe[i]; l=p;
if(l>r) swap(l,r);
sm=pw[r-l]; lp=1; rp=a;
while(lp<rp){
mp=(lp+rp)>>1;
if((hs[r+mp-1]-hs[r-1])==
(hs[l+mp-1]-hs[l-1])*sm) lp=mp+1;
else rp=mp;
}
if(s[r+lp-1]<s[l+lp-1]) l=r; p=l;
}
for(i=1;i<=a;++i) putchar(s[p+i-1]);
return 0;
}

(刚好一共 1024 Tabs)

posted @   Fran-Cen  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示