cw 字符串专题
模板
以下只是对一些知识做总结,写的很简略。
AC 自动机
不会,但我会背板子。
for(int i=0;i<26;i++) ch[0][i]=1;
queue<int> q;q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<26;i++)
if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
else fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
}
马拉车
考虑到一个可能有长度为偶数的回文串,不好搞,我们在每个字符前后都加上一个特殊字符,比如 #。
原串:
abbaba
加入后:
#a#b#b$a$b#a#
这样我们只用考虑
记一个位置
如果
我们找到
然后继承
如图:
否则初始
可以发现拓展的时候
对于边界问题,在字符串边界插入一对不相同的特殊字符即可。
s[0]='?';s[++m]='#';for(int i=1;i<=n;i++) s[++m]=c[i],s[++m]='#';
for(int i=1;i<=m;i++)
{
if(i<=R) r[i]=min(r[p*2-i],R-i+1);
while(s[i-r[i]]==s[i+r[i]]) r[i]++;
if(i+r[i]>R) R=i+r[i]-1,p=i;
}
SA(后缀数组)
定义
显然有
算法
两个字符串比较时哈希+二分,复杂度
倍增,每个后缀只考虑长度为
中间排序的过程可以计数排序,复杂度
减小常数:
- 不用对第 2 关键字排序,考虑按第 2 关键字排序后的数组为 id,我们可以先把后面为空的编号加入 id,再沿用之前排好序的编号依次加入 id。
- 拍完序去重,如果去重后不同排名恰有
个,结束算法。
auto eql=[](int x,int y,int w){return _rk[x]==_rk[y]&&_rk[x+w]==_rk[y+w];};
n=strlen(s+1),m=127;
for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
for(int w=1,k=0;m!=n;w<<=1,m=k,k=0)
{
for(int i=n-w+1;i<=n;i++) id[++k]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) id[++k]=sa[i]-w;
for(int i=0;i<=m;i++) cnt[i]=0;
for(int i=1;i<=n;i++) cnt[rk[id[i]]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
memcpy(_rk,rk,sizeof(rk));k=0;
for(int i=1;i<=n;i++) rk[sa[i]]=eql(sa[i],sa[i-1],w)?k:++k;
}
数组
以下简记为
定义
则
引理:
证明:
为方便观察,下面会直接用方括号表示下标。
当
,显然成立。 当
时,有 则后缀
( )和后缀 有长度为 的 lcp。 不妨设后缀
为 ,后缀 ,其中 是一个字符,大写字母是字符串。 那么显然有
, 可能为空, 非空。 则后缀
为 ,存在后缀 为 。 因为
非空,且后缀 仅比后缀 的排名小一位,那么有 ,即后缀 和后缀 有公共前缀 A。 于是有
。
那么可以线性求。
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;
}
简单应用
- 两子串最长公共前缀:
- 本质不同子串个数:
SAM(后缀自动机)
不是很想写,先丢个板子。
void ins(int c)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1;
for(int i=0;i<26;i++) ch[y][i]=ch[x][i];
while(p&&ch[p][c]==x) ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
}
题目
P2414 阿狸的打字机
可以发现一个结论:
对于 AC 自动机上的一个结点
于是建出失配树(连边
对于一组询问
离线后把询问挂到
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
char c;
int n,m,dn,tot=1,u=1;
int l[N],r[N],id[N],ch[N][26],trie[N][26],fa[N],fail[N],ans[N];
vector<int> G[N],ed[N];
vector<pair<int,int>> q[N];
struct BIT{
int c[N];
void upd(int x,int v) {for(;x<=dn;x+=x&-x) c[x]+=v;}
int qsum(int x) {int r=0;for(;x;x^=x&-x) r+=c[x];return r;}
}tr;
void build()
{
memcpy(trie,ch,sizeof(ch));
for(int i=0;i<26;i++) ch[0][i]=1;
queue<int> q;q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();
G[fail[u]].push_back(u);
for(int i=0;i<26;i++)
if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
else fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
}
}
void dfs(int u)
{
l[u]=++dn;
for(int v:G[u]) dfs(v);
r[u]=dn;
}
void solve(int u)
{
tr.upd(l[u],1);
for(int i:ed[u]) for(auto [x,j]:q[i]) ans[j]=tr.qsum(r[id[x]])-tr.qsum(l[id[x]]-1);
for(int i=0;i<26;i++) if(trie[u][i]) solve(trie[u][i]);
tr.upd(l[u],-1);
}
int main()
{
while((c=getchar())!='\n')
{
if(c=='P') ed[u].push_back(++n),id[n]=u;
else if(c=='B') u=fa[u];
else {c-='a';if(!ch[u][c]) ch[u][c]=++tot;fa[ch[u][c]]=u,u=ch[u][c];}
}
build();dfs(0);
cin>>m;
for(int i=1;i<=m;i++)
{
int x,y;scanf("%d%d",&x,&y);
q[y].push_back({x,i});
}
solve(1);
for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
}
CF1207G Indie Album
同样的套路。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int n,m,tot=1,dn,ch[N][26],fail[N],ans[N],id[N];
char c[N],s[N];
vector<int> G[N],Tf[N],q[N];
struct BIT{
int c[N];
void upd(int x,int v) {for(;x<=dn;x+=x&-x) c[x]+=v;}
int qsum(int x) {int r=0;for(;x;x^=x&-x) r+=c[x];return r;}
}tr;
void insert(int i)
{
int n=strlen(s),u=1;
for(int i=0;i<n;i++)
{
char c=s[i]-'a';
if(!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
}
id[i]=u;
}
void build()
{
for(int i=0;i<26;i++) ch[0][i]=1;
queue<int> q;q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();
Tf[fail[u]].push_back(u);
for(int i=0;i<26;i++)
if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
else fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
}
}
int st[N],ed[N];
void dfs(int u)
{
st[u]=++dn;
for(int v:Tf[u]) dfs(v);
ed[u]=dn;
}
void solve(int x,int u)
{
tr.upd(st[u],1);
for(int i:q[x]) ans[i]=tr.qsum(ed[id[i]])-tr.qsum(st[id[i]]-1);
for(int v:G[x]) solve(v,ch[u][c[v]-'a']);
tr.upd(st[u],-1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
int op,j=0;
cin>>op;if(op==2) cin>>j;cin>>c[i];
G[j].push_back(i);
}
cin>>m;
for(int i=1;i<=m;i++)
{
int x;cin>>x>>s;
insert(i);q[x].push_back(i);
}
build();dfs(0);solve(0,1);
for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
}
CF1437G Death DBMS
又是一道失配树板题。
根据上面失配树的性质,相当于单点修,维护一条树链上的 max 值。树剖即可。
注意到可能有重复串,故用一个 multiset 维护某个结点的 max。
一些细节:失配树以 0 为根,重儿子初始为 -1,同时注意叶子结点不要遍历重儿子导致数组越界。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,tot=1,a[N];
int id[N],ch[N][26],fail[N];
int dn,sz[N],fa[N],son[N],top[N],dfn[N],rev[N];
char s[N];
vector<int> G[N];
multiset<int> S[N];
struct SegTree{
int mx[N*4];
#define lc (k<<1)
#define rc (k<<1|1)
#define mid (l+r>>1)
void build(int k=1,int l=1,int r=dn)
{
mx[k]=-1;
if(l==r) {mx[k]=S[rev[l]].size()?*S[rev[l]].rbegin():-1;return;}
build(lc,l,mid),build(rc,mid+1,r);
mx[k]=max(mx[lc],mx[rc]);
}
void upd(int x,int k=1,int l=1,int r=dn)
{
if(l==r) {mx[k]=S[rev[l]].size()?*S[rev[l]].rbegin():-1;return;}
x<=mid?upd(x,lc,l,mid):upd(x,rc,mid+1,r);
mx[k]=max(mx[lc],mx[rc]);
}
int qmx(int x,int y,int k=1,int l=1,int r=dn)
{
if(l>=x&&r<=y) return mx[k];
int res=-1;
if(x<=mid) res=qmx(x,y,lc,l,mid);
if(mid<y) res=max(res,qmx(x,y,rc,mid+1,r));
return res;
}
}tr;
void build()
{
for(int i=0;i<26;i++) ch[0][i]=1;
queue<int> q;q.push(1);
while(!q.empty())
{
int u=q.front();q.pop();
G[fail[u]].push_back(u);
for(int i=0;i<26;i++)
if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
else fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
}
}
void dfs1(int u)
{
sz[u]=1;son[u]=-1;
for(int v:G[u])
{
fa[v]=u;
dfs1(v);
sz[u]+=sz[v];
if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int topf)
{
dfn[u]=++dn,rev[dn]=u,top[u]=topf;
if(~son[u]) dfs2(son[u],topf);
for(int v:G[u]) if(v!=son[u]) dfs2(v,v);
}
int Qmx(int x)
{
int mx=-1;
while(top[x]) mx=max(mx,tr.qmx(dfn[top[x]],dfn[x])),x=fa[top[x]];
return max(mx,tr.qmx(1,dfn[x]));
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s;
int u=1,len=strlen(s);
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
}
id[i]=u;S[u].insert(0);
}
build();
dfs1(0),dfs2(0,0);tr.build();
while(m--)
{
int op,i,x;cin>>op;
if(op==1)
{
cin>>i>>x;
S[id[i]].erase(S[id[i]].find(a[i]));
S[id[i]].insert(a[i]=x);
tr.upd(dfn[id[i]]);
}
else
{
cin>>s;
int u=1,len=strlen(s),mx=-1;
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
u=ch[u][c];
mx=max(mx,Qmx(u));
}
cout<<mx<<'\n';
}
}
}
P3167 通配符匹配
dp + hash 是最简单的。
注意到通配符数量很小。
定义
假设第
如果 s[p[i]]='*'
,且
设 s[l,r]=t[j+1,j+1+r-l+1]
,那么可以转移到
这个可以用哈希来判断。
由于 ?
不能匹配空字符,我们钦定它必须要匹配一个才可行,即如果 s[p[i+1]]='?'
,转移到的下标还要加一。
当然,为方便,我们保证原串结尾为通配符,就可以直接输出答案。
所以原串后面加一个 ?
,文件串后面加任意一个字符(让这个问号匹配这个字符)。
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=1e5+5;
int n,m,q,k,pos[15],f[15][N];
ull pw[N],hs[N],ht[N];
char s[N],t[N];
int main()
{
scanf("%s",s+1);
n=strlen(s+1);s[++n]='?';
pw[0]=1;for(int i=1;i<=1e5;i++) pw[i]=pw[i-1]*117;
for(int i=1;i<=n;i++)
{
hs[i]=hs[i-1]*117+s[i];
if(s[i]=='?'||s[i]=='*') pos[++k]=i;
}
cin>>q;
while(q--)
{
scanf("%s",t+1);
m=strlen(t+1);t[++m]='a';
for(int i=1;i<=m;i++) ht[i]=ht[i-1]*117+t[i];
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=0;i<=k;i++)
{
if(s[pos[i]]=='*') for(int j=1;j<=m;j++) f[i][j]|=f[i][j-1];
for(int j=0;j<=m;j++)
{
if(!f[i][j]) continue;
int ls=pos[i]+1,rs=pos[i+1]-1;
int lt=j+1,rt=lt+rs-ls;
if(hs[rs]-hs[ls-1]*pw[rs-ls+1]==ht[rt]-ht[lt-1]*pw[rt-lt+1]) f[i+1][rt+(s[pos[i+1]]=='?')]=1;
}
}
f[k][m]?puts("YES"):puts("NO");
}
}
P2336 喵星球上的点名
人类智慧 + hash 是最简单的,跑得也很快。
智慧结论:字符长度
考虑
的字符长度全部出现一遍,那么总长度为 。
对于所有模式串,哈希一下,然后用哈希表存一下出现次数。
那么对于一个字符串
复杂度
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=1e5+5,base=10007;
int n,m,ans[N];
ull pw[N],hs[N],ht[N];
vector<int> v,s[N];
unordered_map<ull,int> cnt,vis,res;
char buf[1<<15],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++)
inline int rd()
{
int x=0,f=1;char c=nc();
for(;!isdigit(c);c=nc()) if(c=='-') f=-1;
for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
int main()
{
n=rd(),m=rd();
for(int i=1;i<=n;i++)
{
int l=rd();
while(l--) s[i].push_back(rd()+1);
s[i].push_back(10002);//中间加个不存在的字符,拼成一个串。
l=rd();
while(l--) s[i].push_back(rd()+1);
}
pw[0]=1;for(int i=1;i<=1e5;i++) pw[i]=pw[i-1]*base;
for(int i=1;i<=m;i++)
{
int l=rd();ull t=0;
v.push_back(l);
while(l--) t=t*base+rd()+1;
ht[i]=t,cnt[t]++;
}
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++)
{
vis.clear();
hs[0]=s[i][0];
for(int j=1;j<s[i].size();j++) hs[j]=hs[j-1]*base+s[i][j];
for(int j=0;j<s[i].size();j++)
for(int len:v)
{
int l=j-len+1,r=j;
if(l<0) break;
ull t=hs[r]-(l-1<0?0:hs[l-1])*pw[len];
if(cnt.count(t)&&!vis.count(t)) ans[i]+=cnt[t],vis[t]=1,res[t]++;
}
}
for(int i=1;i<=m;i++) cout<<res[ht[i]]<<'\n';
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';
}
P3975 弦论
只说
我会枚举!
枚举第
关键是怎么快速计算当前有多少子串。
考虑前
那么在这一段区间,第
设前缀数组为
复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
char s[N];
long long sum[N];
int n,m=127,k,t,sa[N],rk[N],_rk[N],cnt[N],id[N],ht[N];
bool eql(int x,int y,int w) {return _rk[x]==_rk[y]&&_rk[x+w]==_rk[y+w];}
int main()
{
scanf("%s%d%d",s+1,&t,&k);
n=strlen(s+1);
for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
for(int w=1,k=0;m!=n;w<<=1,m=k,k=0)
{
for(int i=n-w+1;i<=n;i++) id[++k]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) id[++k]=sa[i]-w;
for(int i=0;i<=m;i++) cnt[i]=0;
for(int i=1;i<=n;i++) cnt[rk[id[i]]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
memcpy(_rk,rk,sizeof(rk));k=0;
for(int i=1;i<=n;i++) rk[sa[i]]=eql(sa[i],sa[i-1],w)?k:++k;
}
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;
}
int now=0;
if(t)
{
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(n-sa[i]+1);
int L=1,R=n;
for(int i=1;i<=n;i++)
{
for(int j='a';j<='z';j++)
{
int l=L,r=R,res=0;
while(l<=r)
{
int mid=l+r>>1;
if(s[sa[mid]+i-1]<=j) res=mid,l=mid+1;
else r=mid-1;
}
if(!res) continue;
long long t=sum[res]-sum[L-1]-1ll*(res-L+1)*(i-1);
if(k<=res-L+1)
{
for(int k=sa[L];k<=sa[L]+i-1;k++) cout<<s[k];
return 0;
}
if(k<=t) {k-=res-L+1,R=res;break;}
L=res+1,k-=t;
}
if(n-sa[L]+1==i) L++;
}
}
else
{
for(int i=1;i<=n;i++)
{
int _now=now+n-sa[i]+1-ht[i];
if(k<=_now)
{
int l=sa[i],r=sa[i]+ht[i]-1;
while(now!=k) r++,now++;
for(int i=l;i<=r;i++) cout<<s[i];
return 0;
}
now=_now;
}
cout<<-1<<'\n';
}
}
P4094 字符串
这题本来是让用 SA 做的,但我发现 SAM 做更简单(
好像是不能直接得出答案的,不妨二分答案长度为
设前缀
我们看
可以线段树合并来做,注意合并时需要再新建一个点保存信息,因为结点被合并后还可能被访问到。
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
char s[N];
int n,m,id[N],f[N][18];
int tot=1,lst=1,ch[N][26],len[N],fa[N];
vector<int> G[N];
int ins(int c)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1;
for(int c=0;c<26;c++) ch[y][c]=ch[x][c];
while(p&&ch[p][c]==x) ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
return q;
}
#define mid (l+r>>1)
int Tcnt,rt[N],sum[N*60],lc[N*60],rc[N*60];
void upd(int &k,int x,int l=1,int r=n)
{
if(!k) k=++Tcnt;
sum[k]++;
if(l==r) return;
x<=mid?upd(lc[k],x,l,mid):upd(rc[k],x,mid+1,r);
}
int merge(int p,int q,int l=1,int r=n)
{
if(!p||!q) return p+q;
int x=++Tcnt;sum[x]=sum[p]+sum[q];
if(l==r) return x;
lc[x]=merge(lc[p],lc[q],l,mid);
rc[x]=merge(rc[p],rc[q],mid+1,r);
return x;
}
int qsum(int k,int x,int y,int l=1,int r=n)
{
if(!k) return 0;
if(l>=x&&r<=y) return sum[k];
int res=0;
if(x<=mid) res+=qsum(lc[k],x,y,l,mid);
if(mid<y) res+=qsum(rc[k],x,y,mid+1,r);
return res;
}
#undef mid
void dfs(int u)
{
for(int i=1;i<18;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int v:G[u]) f[v][0]=u,dfs(v),rt[u]=merge(rt[u],rt[v]);
}
bool chk(int mid,int a,int b,int c)
{
c=id[c+mid-1];
for(int i=17;~i;i--) if(len[f[c][i]]>=mid) c=f[c][i];
return qsum(rt[c],a+mid-1,b);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>s;
for(int i=1;i<=n;i++) upd(rt[id[i]=ins(s[i-1]-'a')],i);
for(int i=2;i<=tot;i++) G[fa[i]].push_back(i);
dfs(1);
while(m--)
{
int a,b,c,d;cin>>a>>b>>c>>d;
int l=1,r=min(b-a+1,d-c+1),res=0;
while(l<=r)
{
int mid=l+r>>1;
if(chk(mid,a,b,c)) res=mid,l=mid+1;
else r=mid-1;
}
cout<<res<<'\n';
}
}
LOJ6041 事情的相似度
实际就是求
如果我们把
可以用链表维护,每次可以维护删除某个标记,用回滚莫队。
对于维护 max,可以用值域分块,做到
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,T=300;
char s[N];
int n,q,ans[N],st[17][N],pre[N],nxt[N];
int m='1',sa[N],rk[N],_rk[N],id[N],cnt[N],ht[N];
struct node{int l,r,id;} a[N];
bool eql(int x,int y,int w) {return _rk[x]==_rk[y]&&_rk[x+w]==_rk[y+w];}
int qmn(int l,int r){if(!l||!r) return 0;int t=__lg(r-l++);return min(st[t][l],st[t][r-(1<<t)+1]);}
int L[N],R[N],bel[N],c[N],ct[N];
void upd(int x) {if(!x) return;c[x]++,ct[bel[x]]++;}
void ers(int x) {if(!x) return;c[x]--,ct[bel[x]]--;}
int qmx()
{
for(int i=bel[n];~i;i--)
if(ct[i])
{
i=R[i];
while(i&&!c[i]) i--;
return i;
}
return 0;
}
void del(int x)
{
ers(qmn(pre[x],x)),ers(qmn(x,nxt[x])),upd(qmn(pre[x],nxt[x]));
nxt[pre[x]]=nxt[x],pre[nxt[x]]=pre[x];
}
int main()
{
scanf("%d%d%s",&n,&q,s+1);
reverse(s+1,s+1+n);
for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
for(int w=1,k=0;m!=n;w<<=1,m=k,k=0)
{
for(int i=n-w+1;i<=n;i++) id[++k]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) id[++k]=sa[i]-w;
for(int i=0;i<=m;i++) cnt[i]=0;
for(int i=1;i<=n;i++) cnt[rk[id[i]]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];
memcpy(_rk,rk,sizeof(rk));k=0;
for(int i=1;i<=n;i++) rk[sa[i]]=eql(sa[i],sa[i-1],w)?k:++k;
}
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]]=st[0][rk[i]]=k;
}
for(int j=1;j<17;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[j][i]=min(st[j-1][i],st[j-1][i+(1<<j-1)]);
for(int i=1;i<=q;i++)
{
int l,r;scanf("%d%d",&l,&r);
a[i]={n-r+1,n-l+1,i};
}
for(int i=0;i<=n/T;i++)
{
L[i]=max(1,i*T),R[i]=min(n,i*T+T-1);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
}
sort(a+1,a+1+q,[&](node a,node b){return a.l/T==b.l/T?a.r>b.r:a.l<b.l;});
int l,r;
for(int i=1;i<=q;i++)
{
auto [ql,qr,id]=a[i];
if(i==1||a[i].l/T!=a[i-1].l/T)
{
l=L[bel[ql]],r=qr;
for(int i=1;i<=n;i++) cnt[i]=c[i]=ct[i]=0;
for(int i=l;i<=r;i++) cnt[rk[i]]++;
int _pre=0,_nxt=0;
for(int i=1;i<=n;i++) if(cnt[i]) pre[i]=_pre,_pre=i;
for(int i=n;i>=1;i--) if(cnt[i]) nxt[i]=_nxt,_nxt=i;
for(int i=1;i<=n;i++) if(cnt[i]) upd(qmn(pre[i],i));
}
while(qr<r) del(rk[r--]);
int _l=l;
while(ql>l) del(rk[l++]);
ans[id]=qmx();
for(int i=l-1;i>=_l;i--)
{
int x=rk[i];
upd(qmn(pre[x],x)),upd(qmn(x,nxt[x])),ers(qmn(pre[x],nxt[x]));
nxt[pre[x]]=x,pre[nxt[x]]=x;
}
l=_l;
}
for(int i=1;i<=q;i++) cout<<ans[i]<<'\n';
}
P6640 封印
板题。
实际上是询问
对于
这个对
那么对于一个询问,就是求
二分 + ST 表即可。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
char s[N],t[N];
int n,m,q,lst=1,tot=1,ch[N][26],len[N],fa[N],st[20][N];
int qmx(int l,int r) {int t=__lg(r-l+1);return max(st[t][l],st[t][r-(1<<t)+1]);}
void ins(int c)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1;
for(int i=0;i<26;i++) ch[y][i]=ch[x][i];
while(p&&ch[p][c]==x) ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
}
int main()
{
scanf("%s%s%d",s+1,t+1,&q);
n=strlen(s+1),m=strlen(t+1);
for(int i=1;i<=m;i++) ins(t[i]-'a');
int u=1;
for(int i=1;i<=n;i++)
{
int c=s[i]-'a';
while(u!=1&&!ch[u][c]) u=fa[u];
st[0][i]=min(st[0][i-1]+1,len[u]+1);u=ch[u][c];
}
for(int j=1;j<=18;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[j][i]=max(st[j-1][i],st[j-1][i+(1<<j-1)]);
while(q--)
{
int ql,qr;scanf("%d%d",&ql,&qr);
int l=1,r=qr-ql+1,res=0;
while(l<=r)
{
int mid=l+r>>1;
if(qmx(ql+mid-1,qr)>=mid) res=mid,l=mid+1;
else r=mid-1;
}
cout<<res<<'\n';
}
}
CF700E Cool Slogans
感觉非常简单啊,不知道怎么评的 3300。
剪出 SAM,考虑 parent tree 上的一个结点
定义
对于一个结点
-
是 的祖先结点。 -
对于
里的任意一个 endpos,记为 ,那么 的 endpos 集合里一定包含至少一个 里的数。判断这个用线段树合并。
解释一下为什么任意一个 endpos,因为
设
但注意到
如果满足条件,则
否则
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
char s[N];
int n,ans=1,lst=1,tot=1,ch[N][26],len[N],pos[N],fa[N],f[N],g[N];
vector<int> G[N];
void ins(int c,int i)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1,pos[q]=i;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1,pos[y]=i;
for(int i=0;i<26;i++) ch[y][i]=ch[x][i];
while(p&&ch[p][c]==x) ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
}
#define mid (l+r>>1)
int Tcnt,rt[N],lc[N*40],rc[N*40],sum[N*40];
void upd(int &k,int x,int l=1,int r=n)
{
if(!k) k=++Tcnt;
sum[k]++;
if(l==r) return;
x<=mid?upd(lc[k],x,l,mid):upd(rc[k],x,mid+1,r);
}
int merge(int p,int q,int l=1,int r=n)
{
if(!p||!q) return p+q;
int x=++Tcnt;sum[x]=sum[p]+sum[q];
if(l==r) return x;
lc[x]=merge(lc[p],lc[q],l,mid);
rc[x]=merge(rc[p],rc[q],mid+1,r);
return x;
}
int qsum(int k,int x,int y,int l=1,int r=n)
{
if(!k) return 0;
if(l>=x&&r<=y) return sum[k];
if(x<=mid&&qsum(lc[k],x,y,l,mid)) return 1;
if(mid<y&&qsum(rc[k],x,y,mid+1,r)) return 1;
return 0;
}
void dfs(int u) {for(int v:G[u]) dfs(v),rt[u]=merge(rt[u],rt[v]);}
void dp(int u)
{
for(int v:G[u])
{
if(u==1) {f[v]=1,g[v]=v,dp(v);continue;}
if(qsum(rt[g[u]],pos[v]-len[v]+len[g[u]],pos[v]-1)) f[v]=f[u]+1,g[v]=v;
else f[v]=f[u],g[v]=g[u];
ans=max(ans,f[v]);dp(v);
}
}
int main()
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++) ins(s[i]-'a',i),upd(rt[lst],i);
for(int i=2;i<=tot;i++) G[fa[i]].push_back(i);
dfs(1);dp(1);
cout<<ans<<'\n';
}
51nod1600 Simple KMP
刚刚讲了这个题,感觉讲的很烂。。。
设
考虑到这题有后缀自动机的标签,设
枚举最上面那个式子的
求出
考虑如何求原串的某个后缀
先对整个串建出 SAM,枚举当前后缀结尾
考虑
对于 parent tree 上一个结点
算完后插入
树剖维护即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,mod=1e9+7;
char s[N];
ll f[N];
int n,lst=1,tot=1,id[N],ch[N][26],len[N],fa[N];
vector<int> G[N];
int ins(int c)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1;
while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1;
for(int i=0;i<26;i++) ch[y][i]=ch[x][i];
while(p&&ch[p][c]==x) ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
return q;
}
int dn,sz[N],son[N],top[N],dfn[N],rev[N];
void dfs1(int u)
{
sz[u]=1;
for(int v:G[u])
{
dfs1(v);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int topf)
{
dfn[u]=++dn,rev[dn]=u,top[u]=topf;
if(son[u]) dfs2(son[u],topf);
for(int v:G[u]) if(v!=son[u]) dfs2(v,v);
}
#define lc (k<<1)
#define rc (k<<1|1)
#define mid (l+r>>1)
ll sum[N*4];
int val[N*4],add[N*4];
void pushdown(int k)
{
if(!add[k]) return;
add[lc]+=add[k],add[rc]+=add[k];
sum[lc]+=1ll*val[lc]*add[k],sum[rc]+=1ll*val[rc]*add[k];
sum[lc]%=mod,sum[rc]%=mod;
add[k]=0;
}
void build(int k=1,int l=1,int r=tot)
{
if(l==r) {val[k]=len[rev[l]]-len[fa[rev[l]]];return;}
build(lc,l,mid),build(rc,mid+1,r);
val[k]=(val[lc]+val[rc])%mod;
}
int ask(int x,int y,int k=1,int l=1,int r=tot)
{
if(l>=x&&r<=y)
{
int t=sum[k];
add[k]++;sum[k]=(sum[k]+val[k])%mod;
return t;
}
pushdown(k);
int res=0;
if(x<=mid) res+=ask(x,y,lc,l,mid);
if(mid<y) res=(res+ask(x,y,rc,mid+1,r))%mod;
sum[k]=sum[lc]+sum[rc];
return res;
}
int upd(int x)
{
int res=0;
while(top[x]!=1) res=(res+ask(dfn[top[x]],dfn[x]))%mod,x=fa[top[x]];
return (res+ask(1,dfn[x]))%mod;
}
int main()
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++) id[i]=ins(s[i]-'a');
for(int i=2;i<=tot;i++) G[fa[i]].push_back(i);
dfs1(1),dfs2(1,1);build();
for(int i=1;i<=n;i++) f[i]=(f[i-1]+upd(id[i]))%mod;
for(int i=1;i<=n;i++)
{
f[i]=(f[i-1]+f[i])%mod;
cout<<f[i]<<'\n';
}
}
P6292 区间本质不同子串个数
先建出 parent tree。
扫描线,把询问挂到右端点。
从左到右插入字符,对每一个本质不同字串
那么对于一个长度为
考虑每次右端点往右移,相当于把 parent tree 上一条链的
可以用 LCT 维护这个过程,每一个 splay 维护一个相同的
对于询问直接查询
复杂度
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,m;
ll ans[N];
char s[N];
struct node{int l,id;};
vector<node> q[N];
int tot=1,lst=1,id[N],fa[N],len[N],Ch[N][26];
int ins(int c)
{
int p=lst,q=lst=++tot;len[q]=len[p]+1;
while(p&&!Ch[p][c]) Ch[p][c]=q,p=fa[p];
if(!p) fa[q]=1;
else
{
int x=Ch[p][c];
if(len[p]+1==len[x]) fa[q]=x;
else
{
int y=++tot;len[y]=len[p]+1;
for(int i=0;i<26;i++) Ch[y][i]=Ch[x][i];
while(p&&Ch[p][c]==x) Ch[p][c]=y,p=fa[p];
fa[y]=fa[x],fa[x]=fa[q]=y;
}
}
return q;
}
struct BIT{
ll c1[N],c2[N];
void upd(int x,int v) {for(int V=x*v;x<=n;x+=x&-x) c1[x]+=v,c2[x]+=V;}
ll qsum(int x) {ll s1=0,s2=0;for(int y=x;y;y^=y&-y) s1+=c1[y],s2+=c2[y];return (x+1)*s1-s2;}
void upd(int l,int r,int v) {upd(l,v),upd(r+1,-v);}
ll qsum(int l,int r) {return qsum(r)-qsum(l-1);}
}tr;
#define lc(k) ch[k][0]
#define rc(k) ch[k][1]
int top,ch[N][2],val[N],stk[N];
bool isroot(int k) {return lc(fa[k])!=k&&rc(fa[k])!=k;}
bool getson(int k) {return rc(fa[k])==k;}
void pushdn(int k) {if(val[k]) val[lc(k)]=val[rc(k)]=val[k];}
void rotate(int k)
{
int f=fa[k],g=fa[f],p=getson(k),s=ch[k][p^1];
fa[k]=g;
if(!isroot(f)) ch[g][getson(f)]=k;
fa[s]=f,ch[f][p]=s;
fa[f]=k,ch[k][p^1]=f;
}
void splay(int k)
{
stk[top=1]=k;
for(int x=k;!isroot(x);) stk[++top]=(x=fa[x]);
while(top) pushdn(stk[top--]);
while(!isroot(k))
{
int f=fa[k];
if(!isroot(f)) rotate(getson(f)==getson(k)?f:k);
rotate(k);
}
}
void access(int k,int v)
{
int i=0;
for(;k;i=k,k=fa[k])
{
splay(k);
if(val[k]) tr.upd(val[k]-len[k]+1,val[k]-len[fa[k]],-1);
rc(k)=i;
}
val[i]=v;
tr.upd(1,v,1);
}
int main()
{
scanf("%s%d",s+1,&m);
n=strlen(s+1);
for(int i=1;i<=n;i++) id[i]=ins(s[i]-'a');
for(int i=1;i<=m;i++)
{
int l,r;scanf("%d%d",&l,&r);
q[r].push_back({l,i});
}
for(int i=1;i<=n;i++)
{
access(id[i],i);
for(auto [l,id]:q[i]) ans[id]=tr.qsum(l,i);
}
for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
}
本文作者:spider-oyster
本文链接:https://www.cnblogs.com/spider-oyster/p/17878102.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步