P4770 [NOI2018] 你的名字 解题报告
先从
因为很难计算不重复的个数,考虑计算本质不同的与
因为涉及不同的子串数的计算以及匹配,所以可以对
接下来,因为 SAM 的 link 指针和 AC 自动机的 fail 指针很像,可以直接把
此时,我们就可以得到以
记每个节点 endpos 集合中最大的字符串长度为
统计答案时,parent 树上父亲的
接下来考虑加上
用线段树维护后缀自动机的 endpos 集合,如果一个节点的 endpos 包含点 x,就将这个点对应线段树的对应位置改成1
因为 parent 树上的 endpos 是包含关系,可以合并
此时,在转移时判断要走到的点有没有 endpos 在区间
代码:
#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1000005,M=20000005;
int n,T,m;
string s,t;
struct SAM{
int ch[26],link,len;
}s1[N];
int lst=1,tot=1,cnt[N],ids[N];
bool in[N];
void insert(int c)
{
int p=lst,now=++tot;
in[now]=1;
s1[now].len=s1[lst].len+1;
while(p&&!s1[p].ch[c])
{
s1[p].ch[c]=now;
p=s1[p].link;
}
if(!p) s1[now].link=1;
else
{
int q=s1[p].ch[c];
if(s1[p].len+1==s1[q].len)
{
s1[now].link=q;
}
else
{
int clone=++tot;
s1[clone]=s1[q];
s1[clone].len=s1[p].len+1;
while(p&&s1[p].ch[c]==q)
{
s1[p].ch[c]=clone;
p=s1[p].link;
}
s1[q].link=s1[now].link=clone;
}
}
lst=now;
}
int rt[N],ls[M],rs[M],idx;
int update(int p,int l,int r,int x)
{
if(!p) p=++idx;
if(l==r)
{
return p;
}
int mid=(l+r)>>1;
if(x<=mid) ls[p]=update(ls[p],l,mid,x);
else rs[p]=update(rs[p],mid+1,r,x);
return p;
}
int merge(int a,int b)
{
if(!a||!b) return a+b;
int p=++idx;
ls[p]=merge(ls[a],ls[b]);
rs[p]=merge(rs[a],rs[b]);
return p;
}
bool query(int p,int l,int r,int ql,int qr)
{
if(!p||ql>qr) return 0;
if(ql<=l&&qr>=r) return 1;
int mid=(l+r)>>1;
if(ql<=mid&&query(ls[p],l,mid,ql,qr)) return 1;
if(qr>mid&&query(rs[p],mid+1,r,ql,qr)) return 1;
return 0;
}
struct sam{
int ch[26],link,len;
void clear()
{
for(int i=0;i<26;i++) ch[i]=0;
len=link=0;
}
}s2[N*2];
int lst1,tot1,tag[N*2],lim[N*2];
void init()
{
for(int i=0;i<=tot1;i++)
{
s2[i].clear();
}
tot1=lst1=1;
}
void ins(int c)
{
int p=lst1,now=++tot1;
s2[now].len=tag[now]=s2[p].len+1;
while(p&&!s2[p].ch[c])
{
s2[p].ch[c]=now;
p=s2[p].link;
}
if(!p) s2[now].link=1;
else
{
int q=s2[p].ch[c];
if(s2[p].len+1==s2[q].len)
{
s2[now].link=q;
}
else
{
int clone=++tot1;
s2[clone]=s2[q];
s2[clone].len=s2[p].len+1;
tag[clone]=tag[q];
while(p&&s2[p].ch[c]==q)
{
s2[p].ch[c]=clone;
p=s2[p].link;
}
s2[q].link=s2[now].link=clone;
}
}
lst1=now;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>s;
n=s.size();
s=" "+s;
for(int i=1;i<=n;i++) insert(s[i]-'a');
// printf("%d ",tot);
for(int i=1;i<=tot;i++)
{
cnt[s1[i].len]++;
// printf("%d ",s1[i].len);
}
for(int i=1;i<=tot;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=tot;i++) ids[cnt[s1[i].len]--]=i;
for(int i=tot;i>0;i--)
{
int x=ids[i];
// cout<<in[x]<<" "<<x<<'\n';
if(in[x]) rt[x]=update(rt[x],1,n,s1[x].len);
rt[s1[x].link]=merge(rt[s1[x].link],rt[x]);
}
cin>>T;
while(T--)
{
int l,r;
cin>>t>>l>>r;
m=t.size();
t=" "+t;
init();
int p=1,len=0;
for(int i=1;i<=m;i++)
{
int c=t[i]-'a';
ins(c);
while(1)
{
if(s1[p].ch[c]&&query(rt[s1[p].ch[c]],1,n,l+len,r))
{
len++,p=s1[p].ch[c];
break;
}
if(len==0) break;
len--;
if(len==s1[s1[p].link].len)
p=s1[p].link;
}
lim[i]=len;
}
ll ans=0;
for(int i=2;i<=tot1;i++)
{
ans=ans+max(0,s2[i].len-max(s2[s2[i].link].len,lim[tag[i]]));
}
cout<<ans<<'\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)