NOI2018 你的名字 后缀自动机_线段树合并_可持久化
相当复杂的一道题,同样也相当优美。
考察的知识点很多:
权值线段树的可持久化合并,后缀自动机,后缀树...
考察的知识点很多:
权值线段树的可持久化合并,后缀自动机,后缀树...
考虑 $68pts$ $l=1,r=|s|$的数据:
这部分相对好做一些,不过思维难度对我来说已经不小。但是一旦解出这一部分之后离满分算法就不远了。
设 ION2017 为 $S$,ION2018 为 $T$。
我们想求 $T$ 中出现的子串且在 $S$ 中未出现的种类(即重复的只算一个)。
直接求未出现的种类似乎有些苦难,我们这么考虑:先求出 $T$ 中的所有种类,再减掉与 $S$ 重合的部分,这样似乎能好做一些。
求 $T$ 的所有种类很很好做:对 $T$ 构建后缀自动机,种类数即为 $\sum_{i=1}^{tot} dis[i]-dis[f[i]]$(SDOI生成魔咒)
考虑如何求取相同部分。
我们可以将 $T$ 串在 $S$ 的后缀自动机上跑一遍,每新加入一个字符就沿着转移边和适配边走。成功转移,就让记录匹配长度的计数器++,否则就让计数器等于失配节点的最长长度(这里一定是最长)。 这样,对于 $T$ 中的每个下标,我们能求出它的后缀再 $S$ 中以子串出现的最长长度。将 $T$ 中下标为 $i$ 的最长匹配后缀设为 $mx[i]$。
是不是认为答案就是类似于 $\sum_{i=1}^{strlen(T)} i-mx[i]$ ?
确实是类似的。为了使统计不出现重复,我们可以再 $T$ 的后缀自动机上进行统计(因为后缀自动机上每个节点表示的字符串集合都是互不相同的)
考虑一下后缀自动机插入的过程:新加入的 $np$ 节点的 $dis$ 就是等于该字符再原字符串中出现的位置,$nq$ 出现的位置应该与 $q$ 是相同的(原先 $nq$ 是被 $q$ 所包含的,即原先应该相同,新加入也必相同)。我们可以由此得到每个节点第一次在原字符串(即 $T$ ) 中出现的位置。那么,也就是说后缀自动机上每个节点都可以代表原串的某个后缀。而该节点所代表的后缀个数就是 $dis[p]-dis[f[p]]$。
说到这里,我们将这个和之前的 $mx[i]$ 联系起来,一个点产生的贡献其实就是 $dis[i]-max(dis[f[i]],mx[pos[i]])$ 其中,$pos$ 数组为前文所说该节点对应的原字符串中出现的位置。 而我们取 $max$ 是因为有可能该贡献不由点 $p$ 记录,而是由 $p$ 在后缀中的父亲所记录。 当然,我们还要与 $0$ 再去一个 $max$,因为结果可能为负(可以想一想,为什么)
综上所述,答案即为 $ans=\sum_{i=1}^{tot} max(0,dis_{i}-max(dis[f_{i}],mx[pos_{i}]))$
这部分相对好做一些,不过思维难度对我来说已经不小。但是一旦解出这一部分之后离满分算法就不远了。
设 ION2017 为 $S$,ION2018 为 $T$。
我们想求 $T$ 中出现的子串且在 $S$ 中未出现的种类(即重复的只算一个)。
直接求未出现的种类似乎有些苦难,我们这么考虑:先求出 $T$ 中的所有种类,再减掉与 $S$ 重合的部分,这样似乎能好做一些。
求 $T$ 的所有种类很很好做:对 $T$ 构建后缀自动机,种类数即为 $\sum_{i=1}^{tot} dis[i]-dis[f[i]]$(SDOI生成魔咒)
考虑如何求取相同部分。
我们可以将 $T$ 串在 $S$ 的后缀自动机上跑一遍,每新加入一个字符就沿着转移边和适配边走。成功转移,就让记录匹配长度的计数器++,否则就让计数器等于失配节点的最长长度(这里一定是最长)。 这样,对于 $T$ 中的每个下标,我们能求出它的后缀再 $S$ 中以子串出现的最长长度。将 $T$ 中下标为 $i$ 的最长匹配后缀设为 $mx[i]$。
是不是认为答案就是类似于 $\sum_{i=1}^{strlen(T)} i-mx[i]$ ?
确实是类似的。为了使统计不出现重复,我们可以再 $T$ 的后缀自动机上进行统计(因为后缀自动机上每个节点表示的字符串集合都是互不相同的)
考虑一下后缀自动机插入的过程:新加入的 $np$ 节点的 $dis$ 就是等于该字符再原字符串中出现的位置,$nq$ 出现的位置应该与 $q$ 是相同的(原先 $nq$ 是被 $q$ 所包含的,即原先应该相同,新加入也必相同)。我们可以由此得到每个节点第一次在原字符串(即 $T$ ) 中出现的位置。那么,也就是说后缀自动机上每个节点都可以代表原串的某个后缀。而该节点所代表的后缀个数就是 $dis[p]-dis[f[p]]$。
说到这里,我们将这个和之前的 $mx[i]$ 联系起来,一个点产生的贡献其实就是 $dis[i]-max(dis[f[i]],mx[pos[i]])$ 其中,$pos$ 数组为前文所说该节点对应的原字符串中出现的位置。 而我们取 $max$ 是因为有可能该贡献不由点 $p$ 记录,而是由 $p$ 在后缀中的父亲所记录。 当然,我们还要与 $0$ 再去一个 $max$,因为结果可能为负(可以想一想,为什么)
综上所述,答案即为 $ans=\sum_{i=1}^{tot} max(0,dis_{i}-max(dis[f_{i}],mx[pos_{i}]))$
Code:
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 5000000
#define N 30
#define ll long long
#define setIO(s) freopen(s".in","r",stdin) ,freopen(s".out","w",stdout)
using namespace std;
char str[maxn],ss[maxn];
int str_len,ss_len;
int nodes;
int C[maxn],rk[maxn];
int rt[maxn];
int pos[maxn],mx[maxn];
struct Segment_Tree{ int l,r,sumv; }node[maxn<<2];
int newnode(){ return ++nodes; }
void modify(int p,int l,int r,int &o){
if(!o) o=newnode();
++node[o].sumv;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) modify(p,l,mid,node[o].l);
else modify(p,mid+1,r,node[o].r);
}
int merge(int x,int y){
if(!x||!y) return x+y;
int o=newnode();
node[o].sumv=node[x].sumv+node[y].sumv;
node[o].l=merge(node[x].l,node[y].l);
node[o].r=merge(node[x].r,node[y].r);
return o;
}
int query(int l,int r,int L,int R,int o){
if(l>r||r<L||l>R) return 0;
if(l>=L&&r<=R) return node[o].sumv;
int mid=(l+r)>>1,res=0;
if(node[o].l) res+=query(l,mid,L,R,node[o].l);
if(node[o].r) res+=query(mid+1,r,L,R,node[o].r);
return res;
}
struct SAM{
int last,tot,dis[maxn],ch[maxn][N],f[maxn],pos[maxn];
void init() { last=++tot; }
void ins(int c,int y,int z,int rot){
int p=last,np=++tot; last=np; dis[np]=dis[p]+1; pos[np] = z;
while(p&&!ch[p][c])ch[p][c]=np,p=f[p];
if(!p) f[np]=rot;
else{
int q=ch[p][c],nq;
if(dis[q]==dis[p]+1) f[np]=q;
else{
nq=++tot;
dis[nq]=dis[p]+1;
pos[nq] = pos[q];
memcpy(ch[nq],ch[q],sizeof(ch[q]));
f[nq]=f[q],f[q]=f[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p];
}
}
if(y) modify(y,1,str_len,rt[np]);
}
void build_S1(){
for(int i=1;i<=tot;++i) C[dis[i]]++;
for(int i=1;i<=tot;++i) C[i]+=C[i-1];
for(int i=1;i<=tot;++i) rk[C[dis[i]]--]=i;
for(int i=tot;i>=1;--i) {
int p=rk[i];
rt[f[p]] = merge(rt[f[p]],rt[p]);
}
}
}S1,S2;
int main(){
//setIO("input");
scanf("%s",str+1),str_len=strlen(str+1),S1.init();
for(int i=1;i<=str_len;++i) S1.ins(str[i]-'a',i,0,1);
S1.build_S1();
int queries,l,r,st,ed;
scanf("%d",&queries);
while(queries--) {
S2.init(),st=S2.tot,scanf("%s%d%d",ss+1,&l,&r),ss_len=strlen(ss+1);
for(int i=1;i<=ss_len;++i) S2.ins(ss[i]-'a',0,i,st);
ed=S2.tot;
int cnt=0,p=1;
long long ans = 0;
for(int i=1;i<=ss_len;++i) {
int c=ss[i]-'a';
while(1){
if(S1.ch[p][c] && query(1,str_len,l+cnt,r,rt[S1.ch[p][c]]))
{
++cnt,p=S1.ch[p][c]; break;
}
else {
if(!cnt) {p=1;break; }
--cnt;
if(cnt==S1.dis[S1.f[p]]) p=S1.f[p];
}
}
mx[i]=cnt;
}
for(int i=st;i<=ed;++i)
ans+=max(0,S2.dis[i]-max(mx[S2.pos[i]],S2.dis[S2.f[i]]));
printf("%lld\n",ans);
}
return 0;
}