【题解】P4770 [NOI2018] 你的名字
\(l=1,r=|S|\) 给了 \(68\) 分,先考虑这种情况。
本题询问的是 \(S\) 的子串,对它建立 sam 看看。
把 \(T\) 扔掉 \(S\) 的 sam 上匹配,用 SP1811 LCS - Longest Common Substring 类似的方式,设 \(lim_i\) 表示 \(T\) 的前 \(i\) 位走完以后,\(T\) 匹配的最长后缀,即 \(T[i-lim[i]+1:i]\) 是 \(S\) 的子串。
\(lim_i\) 的求法如下:\(T\) 按照顺序每一位去匹配,如果当前点 \(u\) 存在这一位的转移边,那么 \(lim_i\larr lim_{i-1}+1\),否则不断跳 \(u\) 的fail树上祖先,直到存在一点 \(v\) 有该位的转移边,由于 \(v\) 内的点都是 \(u\) 的后缀,所以直接将 \(lim_i\larr len(v)+1\),如果不存在任何一点 \(v\) 有该位的转移边,那么 \(lim_i\larr 0\)。
这样暴力跳fail的时间复杂度看似不对,我们来稍微分析一下。\(lim_i\) 在整个过程中不会增加超过 \(|T|\) 次,因此 \(lim_i\) 减少的次数不会超过 \(|T|\) 次,而每次跳 \(fail\) 时,\(len\) 会减小,于是对应可能的 \(lim_i\) 也会减小。而减小总次数不会超过 \(|T|\) 次,即跳fail操作不会进行超过 \(|T|\) 次,时间复杂度 \(O(|T|)\) 。
把 \(lim\) 求出来了以后,题目要我们求出若干本质不同的子串数量,于是放在 \(T\) 中去看,我们还需要构建 \(T\) 的 sam。 考虑 \(T\) 的 sam 上的每一个点带来的贡献,因为每个点 \(u\) 中代表的字符串是一些长度连续的 \(longest(u)\) 的后缀,这部分字符串可以拆成两组,一组是在 \(S\) 中完全出现过的,另一组是没有完全出现过的,且两组内部字符串长度都连续。考虑如何求出这个分界线,假设我们知道这些字符串在 \(T\) 中出现的任何一个结束位置 \(x\) ,我们就可以通过 \(lim_{x}\) 知道匹配长度。我们可以令 \(x\) 为最早出现位置方便转移。于是每个点 \(u\) 的贡献如下:
原题 \(68\) 分 代码 (洛谷 \(54\) 分):
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=500009;
struct Node{
ll ch[26],len,fail,tag;
};
#define ch(u,c) tr[u].ch[c]
#define fail(u) tr[u].fail
#define len(u) tr[u].len
#define tag(u) tr[u].tag
struct sam{
ll lst,tot;
Node tr[2*N];
sam(){
lst=tot=1;
}
inline void clear(){
for(ll i=0;i<=tot;i++){
len(i)=fail(i)=tag(i)=0;
for(ll j=0;j<26;j++) ch(i,j)=0;
}
lst=tot=1;
}
inline void insert(ll c){
ll p=lst,np=++tot;
lst=np;
len(np)=len(p)+1;
tag(np)=len(np);
for(;p&&!ch(p,c);p=fail(p)) ch(p,c)=np;
if(!p) fail(np)=1;
else{
ll q=ch(p,c);
if(len(q)==len(p)+1){
fail(np)=q;
}
else{
ll nq=++tot; tr[nq]=tr[q];
len(nq)=len(p)+1;
fail(np)=fail(q)=nq;
for(;p&&ch(p,c)==q;p=fail(p)){
ch(p,c)=nq;
}
}
}
}
} samS,samT;
ll lim[N];
inline void match(string &t){
ll u=1;
for(ll i=1;i<t.size();i++){
ll c=t[i]-'a';
if(samS.ch(u,c)){
lim[i]=lim[i-1]+1;
u=samS.ch(u,c);
}
else{
for(;u&&!samS.ch(u,c);u=samS.fail(u));
if(!u) lim[i]=0,u=1;
else{
lim[i]=samS.len(u)+1;
u=samS.ch(u,c);
}
}
}
}
string s,t;
ll nQ,l,r;
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>s;
s=" "+s;
for(ll i=1;i<s.size();i++){
samS.insert(s[i]-'a');
}
cin>>nQ;
while(nQ--){
cin>>t>>l>>r;
t=" "+t;
samT.clear();
match(t);
for(ll i=1;i<t.size();i++){
samT.insert(t[i]-'a');
}
ll ans=0;
for(ll u=2;u<=samT.tot;u++){
ans+=max(0ll,samT.len(u)-max(samT.len(samT.fail(u)),lim[samT.tag(u)]));
}
cout<<ans<<"\n";
}
return 0;
}
再考虑 \(l,r\) 任意的情况。一般来说处理区间问题需要维护每个点的 \(endpos\) 集合,使用线段树合并即可。看一下 \(lim\) 产生的变化。我们在求 \(lim\) 的时候,需要看一下转移后的点是否存在 \(endpos\in[l+lim,r]\) ,如果存在那么不影响,如果不存在则我们需要不断跳fail知道满足条件,注意这里 \(len\) 也要改为,查找该点 \(endpos\) 中在 \([1,r]\) 的最大值 \(k\),然后用原先的 \(len\) 和 \(k-l+1\) 取 \(\min\) 才是需要的 \(len\)。也可以无脑直接让 \(lim\) 不断缩小,直到变为 \(fail\) 的 \(len\) 再转移。
时间复杂度 \(O((|S|+|T|)\log |S|)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=500009;
struct Node{
ll ch[26],len,fail,tag,vst;
};
#define ch(u,c) tr[u].ch[c]
#define fail(u) tr[u].fail
#define len(u) tr[u].len
#define tag(u) tr[u].tag
#define vst(u) tr[u].vst
struct Segment{
ll ls,rs;
} tr[N*40];
ll cnt;
ll rt[N*2];
#define ls(u) tr[u].ls
#define rs(u) tr[u].rs
inline void upd(ll &u,ll l,ll r,ll x){
if(!u) u=++cnt;
if(l==r) return;
ll mid=(l+r)/2;
if(x<=mid) upd(ls(u),l,mid,x);
else upd(rs(u),mid+1,r,x);
}
inline ll merge(ll x,ll y){
if(!x||!y) return x+y;
ll u=++cnt;
ls(u)=merge(ls(x),ls(y));
rs(u)=merge(rs(x),rs(y));
return u;
}
inline bool query(ll u,ll l,ll r,ll ql,ll qr){
if(!u||ql>qr) return 0;
if(ql<=l&&r<=qr) return 1;
ll mid=(l+r)/2;
if(ql<=mid){
if(query(ls(u),l,mid,ql,qr)) return 1;
}
if(mid<qr){
if(query(rs(u),mid+1,r,ql,qr)) return 1;
}
return 0;
}
string s;
struct sam{
ll lst,tot;
Node tr[2*N];
sam(){
lst=tot=1;
}
inline void clear(){
for(ll i=0;i<=tot;i++){
len(i)=fail(i)=tag(i)=vst(i)=0;
for(ll j=0;j<26;j++) ch(i,j)=0;
}
lst=tot=1;
}
inline void insert(ll c){
ll p=lst,np=++tot;
lst=np;
len(np)=len(p)+1;
tag(np)=len(np),vst(np)=1;
for(;p&&!ch(p,c);p=fail(p)) ch(p,c)=np;
if(!p) fail(np)=1;
else{
ll q=ch(p,c);
if(len(q)==len(p)+1){
fail(np)=q;
}
else{
ll nq=++tot; tr[nq]=tr[q];
len(nq)=len(p)+1;
vst(nq)=0;
fail(np)=fail(q)=nq;
for(;p&&ch(p,c)==q;p=fail(p)){
ch(p,c)=nq;
}
}
}
}
ll bin[2*N],xs[2*N];
inline void build(){
for(ll i=1;i<=tot;i++) bin[len(i)]++;
for(ll i=1;i<=tot;i++) bin[i]+=bin[i-1];
for(ll i=1;i<=tot;i++) xs[bin[len(i)]--]=i;
for(ll i=tot;i>=1;i--){
ll p=xs[i];
if(vst(p)) upd(rt[p],1,s.size()-1,len(p));
rt[fail(p)]=merge(rt[fail(p)],rt[p]);
}
}
} samS,samT;
ll lim[N];
inline void match(string &t,ll l,ll r){
ll u=1,len=0;
for(ll i=1;i<t.size();i++){
ll c=t[i]-'a';
while(1){
if(samS.ch(u,c)&&query(rt[samS.ch(u,c)],1,s.size()-1,l+len,r)){
len++;
u=samS.ch(u,c);
break;
}
if(u==1) break;
len--;
if(len==samS.len(samS.fail(u))){
u=samS.fail(u);
}
}
lim[i]=len;
}
}
string t;
ll nQ,l,r;
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>s;
s=" "+s;
for(ll i=1;i<s.size();i++){
samS.insert(s[i]-'a');
}
samS.build();
cin>>nQ;
while(nQ--){
cin>>t>>l>>r;
t=" "+t;
samT.clear();
match(t,l,r);
for(ll i=1;i<t.size();i++){
samT.insert(t[i]-'a');
}
ll ans=0;
for(ll u=2;u<=samT.tot;u++){
ans+=max(0ll,samT.len(u)-max(samT.len(samT.fail(u)),lim[samT.tag(u)]));
}
cout<<ans<<"\n";
}
return 0;
}

浙公网安备 33010602011771号