[NOI2018]你的名字
苍天啊,大地啊,模拟赛里怎么全是毒瘤题啊!
题目链接:luoguP4770。
这道题让我们求的是 $ t $ 所有本质不同子串中不为 $ S[l,r] $ 的子串的个数。
首先我们可以利用 SAM 非常简单的求出 $ t $ 的本质不同子串个数。
什么?你不知道怎么求,建议先做luoguP3804 【模板】后缀自动机 (SAM)
下面我们只需要对每一个询问求出包含在 $ S[l,r] $ 中的 $ t $ 的所有本质不同子串个数就可以了。
68分做法
我们可以看到,整道题前面 $ 68 $ 分都有 $ l=1,r=|S| $ ,我们不妨先来思考这些测试点怎么做。
如果我们想要知道 $ t $ 的哪些子串同时也是 $ S $ 的子串,最好的方法肯定就是对 $ |S| $ 建立一个 SAM,然后用 $ t $ 在上面匹配。
因为SAM的fail的含义于AC自动机的差不多,指向的都是该节点代表字符串的后缀。
并且SAM中包含一个字符串的所有子串,就相当于是把一个字符串的所有子串抽出来建了一个AC自动机。
所以实际上是可以进行匹配的(可能还挺常用?)。
这样子我们就可以知道对于 $ t $ 的每一个前缀 $ t[1,i] $ ,它的最长后缀的长度使得这个后缀是 $ S $ 的子串。
但是这样找出的子串并不能保证本质不同。
那么我们把这个过程搬到 SAM 上面。
我们再对 $ t $ 建一个 SAM,并且对于每一个点 $ i $ 再新维护一个信息 $ val_i $ ,表示第 $ i $ 个点表示的所有字符串中,长度小于等于 $ val_i $ 的都是 $ S $ 的子串。
我们只需要把 $ t $ 在 $ S $ 的 SAM 上面跑一遍,然后顺着 parent 边更新答案就可以了(拓扑排序或者dfs都行)。
其实上面找 $ val_i $ 的过程事实上就是这道题,可以做一下加深理解。
然后我们就可以拿到 $ 68 $ 分了!
满分做法
我们发现,上面的做法不能AC主要原因在于每一次询问的是 $ S $ 的一个子串,我们使用 $ t $ 进行匹配的时候很有可能“越界”,而我们又不能快速得到 $ S $ 的某个子串的SAM。
我们无法快速得到一个子串的SAM,那我们能否快速查询某一个节点代表的某个子串是否在 $ [l,r] $ 范围内呢?
我们知道,SAM上某一个节点代表字符串的位置实际上是和它的 endpos 有关的,那么我们能不能快速得到某个节点的 endpos 集合呢?
答案是可以的,我们需要在SAM的parent上面跑线段树合并。
所以我们只需要在沿边转移的时候加上一个endpos的条件即可。
停停,线段树合并不是离线的吗?
所以我们需要进行可持久化线段树合并。
和普通线段树合并不同的一点是,我们每次合并两个节点时新建一个节点,这样就不会破坏原来的节点,并且在所有查询之前进行合并,这样时间复杂度不会变,算法也变成了在线。
然后快乐提交,拿到 $ 97 $ 分,挂了一个点。
哪里挂了呢?
你会发现假如某一个节点因为endpos而不能转移时,你直接跳了fail,但是有可能在匹配长度减小一些以后它又可以转移了。
稍微转移一下就可以过。
代码:
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
template<typename A>
using vc=vector<A>;
using pi=pair<int,int>;
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int root[1000001];
vc<int>g[1000001];
int ls[30000001];
int rs[30000001];
int son[2000001][26];
int fail[2000001];
int len[2000001];
vc<int>t[100001];
vc<int>w[100001];
char in[1000002];
int mo[2000001];
int ma[2000001];
char s[500002];
ll ans[100001];
int l[100001];
int r[100001];
int cnt=1;
int lst=1;
int tot;
int n,m;
void clear()
{
for(int i=1;i<=cnt;i++)
{
fail[i]=len[i]=ma[i]=0;
for(int j=0;j<26;j++) son[i][j]=0;
}
cnt=lst=1;
}
void add(int a)
{
int cur=++cnt,p=lst;lst=cur,len[cur]=len[p]+1;
for(;p&&!son[p][a];p=fail[p]) son[p][a]=cur;
if(!p){ fail[cur]=1;return ;}
int q=son[p][a];
if(len[q]==len[p]+1){ fail[cur]=q;return ;}
int nq=++cnt;len[nq]=len[p]+1;
fail[nq]=fail[q],fail[q]=fail[cur]=nq;
for(int i=0;i<26;i++) son[nq][i]=son[q][i];
for(;p&&son[p][a]==q;p=fail[p]) son[p][a]=nq;
}
void debug()
{
// for(int i=1;i<=cnt;i++)
// {
// printf("%d : len=%d fail=%d ",i,len[i],fail[i]);
// for(int j=0;j<26;j++) if(son[i][j]) printf("(%c,%d) ",j+'a',son[i][j]);
// printf("\n");
// }
}
void add(int &p,int pl,int pr,int x)
{
if(!p) p=++tot;
if(pl==pr) return ;
int mid=(pl+pr)>>1;
if(x<=mid) add(ls[p],pl,mid,x);
else add(rs[p],mid+1,pr,x);
}
int merge(int u,int v,int pl,int pr)
{
if(!u||!v) return u|v;
int ans=++tot;
if(pl==pr) return ans;
int mid=(pl+pr)>>1;
ls[ans]=merge(ls[u],ls[v],pl,mid);
rs[ans]=merge(rs[u],rs[v],mid+1,pr);
return ans;
}
void dfs(int num)
{
for(int p:g[num]) dfs(p),root[num]=merge(root[num],root[p],1,m);
}
void topo()
{
queue<int>que;
for(int i=2;i<=cnt;i++) mo[fail[i]]++;
for(int i=1;i<=cnt;i++) if(!mo[i]) que.push(i);
while(!que.empty())
{
int p=que.front();que.pop();
ma[fail[p]]=max(ma[fail[p]],ma[p]),mo[fail[p]]--;
if(!mo[fail[p]]) que.push(fail[p]);
}
}
bool get(int p,int pl,int pr,int l,int r)
{
// printf("get %d %d %d %d %d\n",p,pl,pr,l,r);
if(!p) return false;
if(l<=pl&&pr<=r) return true;
int mid=(pl+pr)>>1;
if(l<=mid&&get(ls[p],pl,mid,l,r)) return true;
if(mid<r&&get(rs[p],mid+1,pr,l,r)) return true;
return false;
}
int main()
{
// freopen("name.in","r",stdin);
// freopen("name.out","w",stdout);
scanf("%s",s+1),n=read(),m=strlen(s+1);
for(int i=1;s[i];i++) add(s[i]-'a');
for(int i=1,now=1;s[i];i++) now=son[now][s[i]-'a'],add(root[now],1,m,i);
for(int i=2;i<=cnt;i++) g[fail[i]].push_back(i);
dfs(1),debug();
// for(int i=1;i<=cnt;i++) printf("%d ",root[i]);
// printf("\n");
for(int i=1;i<=n;i++)
{
scanf("%s",in+1);int l=read(),r=read(),now=1,le=0;
for(int j=1;in[j];j++)
{
t[i].push_back(in[j]-'a');
while(true)
{
if(son[now][in[j]-'a']&&le<=r-l&&get(root[son[now][in[j]-'a']],1,m,l+le,r))
{
now=son[now][in[j]-'a'],le++;
break;
}
if(!le) break;
// printf("le : %d -> %d\n",le,le-1);
le--;
if(len[fail[now]]==le) now=fail[now];
}
w[i].push_back(le);
// printf("j=%d : now=%d le=%d\n",j,now,le);
// printf("%d ",le);
}
// printf("\n");
}
for(int i=1;i<=n;i++)
{
clear();
for(unsigned j=0;j<t[i].size();j++) add(t[i][j]);
for(int j=0,now=1;j<(int)t[i].size();j++)
{
now=son[now][t[i][j]];
ma[now]=max(ma[now],w[i][j]);
// printf("%d : %d\n",now,w[i][j]);
}
topo();
for(int j=2;j<=cnt;j++)
{
ans[i]-=max(0,min(len[j],ma[j])-len[fail[j]]);
ans[i]+=len[j]-len[fail[j]];
}
}
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}
/*
scbamgepe
1
sgepe 1 9
*/
本文来自博客园,作者:Wuyanru,转载请取得作者同意并注明原文链接:https://www.cnblogs.com/Wuyanru/p/NOI2018-your_name.html
本文内容如有侵权行为,请联系作者删除,qq:1395170470