bzoj 5417 NOI2018 你的名字
神题啊...
首先我们考虑68分的暴力:
对于询问串的每个位置$i$,我们维护一个$lim_{i}$表示以$i$为终点向前至多能与模式串匹配多长,这一点可以在把询问串放在模式串的后缀自动机上跳跃得到
接下来考虑统计答案:
对于询问串同样建起一个后缀自动机,我们知道后缀自动机上的每个节点维护的位置是一些长度连续的子串,同时对应有一个相同的$endpos$集合,因此我们记录下每个节点$endpos$集合中的第一个位置$tag$,那么这个节点$p$对答案的贡献即为$max(0,len{p}-max(len_{f_{p}},lim_{tag_{p}}))$
为什么?
我们考虑这个节点维护的子串数量即为$len_{p}-len_{f_{p}}$,而同时最多向前匹配的长度是$lim_{tag_{p}}$,因此这个点合法的子串数量是$len_{p}-lim_{tag_{p}}$,那么两个取最小值就是这个点的贡献,也即减去$max(len_{f_{p}},lim_{tag_{p}})$
这样的话,我们只需在模式串的后缀自动机上跑一次询问串,然后遍历一次询问串的后缀自动机即可
接下来考虑正解:
仍然建起两个后缀自动机,唯一的区别只是在匹配的时候被匹配到的点必须在询问给出的区间之中
那么我们对于模式串就要维护每个节点的整个$endpos$集合,这样我们在匹配时就要确保跳到的点$endpos$集合中至少有一个在询问区间之中
因此我们对模式串上的后缀自动机每个节点开一棵权值线段树,用线段树合并维护每个节点的$endpos$集合即可
查询时查询区间$[l+当前匹配长度,r]$
注意这时由于我们可以令当前匹配长度连续变化,因此如果失配只是匹配长度减一继续匹配
如果匹配长度减到了当前节点的$pre$的len,再跳$pre$指针
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #include <vector> #define ll long long using namespace std; char S[500005],T[500005]; int lens,lent,siz; int lim[500005]; vector <int> v[1000005]; int ls[40000005],rs[40000005],rot[1000005]; int Tt; void update(int &rt,int l,int r,int pos) { if(!rt)rt=++siz; if(l==r)return; int mid=(l+r)>>1; if(pos<=mid)update(ls[rt],l,mid,pos); else update(rs[rt],mid+1,r,pos); } int merge(int x,int y) { if(!x||!y)return x|y; int ret=++siz; ls[ret]=merge(ls[x],ls[y]); rs[ret]=merge(rs[x],rs[y]); return ret; } bool query(int rt,int l,int r,int lq,int rq) { if(!rt)return 0; if(l>=lq&&r<=rq)return 1; int mid=(l+r)>>1; bool ret=0; if(ls[rt]&&lq<=mid)ret|=query(ls[rt],l,mid,lq,rq); if(rs[rt]&&rq>mid)ret|=query(rs[rt],mid+1,r,lq,rq); return ret; } struct SAM { int tranc[1000005][27]; int len[1000005],pre[1000005],tag[1000005]; int siz; int tot,las; void init() { for(int i=1;i<=tot;i++)memset(tranc[i],0,sizeof(tranc[i])),len[i]=pre[i]=tag[i]=0; tot=las=1; } void ins(int c) { int nwp=++tot; len[nwp]=len[las]+1; tag[nwp]=len[nwp]; int lsp; for(lsp=las;lsp&&!tranc[lsp][c];lsp=pre[lsp])tranc[lsp][c]=nwp; if(!lsp)pre[nwp]=1; else { int lsq=tranc[lsp][c]; if(len[lsq]==len[lsp]+1){pre[nwp]=lsq;las=nwp;return;} int nwq=++tot; tag[nwq]=tag[lsq]; len[nwq]=len[lsp]+1; pre[nwq]=pre[lsq],pre[lsq]=pre[nwp]=nwq; memcpy(tranc[nwq],tranc[lsq],sizeof(tranc[lsq])); while(lsp&&tranc[lsp][c]==lsq)tranc[lsp][c]=nwq,lsp=pre[lsp]; } las=nwp; } void buildtree() { for(int i=2;i<=tot;i++)v[pre[i]].push_back(i); } void dfs(int x) { update(rot[x],1,lens,tag[x]); for(int i=0;i<v[x].size();i++) { int to=v[x][i]; dfs(to); rot[x]=merge(rot[x],rot[to]); } } void match(char *p,int lq,int rq) { int now=1,mlen=0; for(int i=1;i<=lent;i++) { int c=p[i]-'a'+1; while(1) { if(tranc[now][c]&&query(rot[tranc[now][c]],1,lens,lq+mlen,rq)){mlen++,now=tranc[now][c];break;} if(!mlen)break; mlen--; if(mlen==len[pre[now]])now=pre[now]; } lim[i]=mlen; } } }s,t; int main() { // freopen("test.in","r",stdin); scanf("%s",S+1); lens=strlen(S+1); s.init(); for(int i=1;i<=lens;i++)s.ins(S[i]-'a'+1); s.buildtree(),s.dfs(1); scanf("%d",&Tt); while(Tt--) { scanf("%s",T+1); int lq,rq; scanf("%d%d",&lq,&rq); lent=strlen(T+1); t.init(); for(int i=1;i<=lent;i++)t.ins(T[i]-'a'+1); s.match(T,lq,rq); ll ans=0; for(int i=2;i<=t.tot;i++)ans+=1ll*max(0,t.len[i]-max(t.len[t.pre[i]],lim[t.tag[i]])); printf("%lld\n",ans); } return 0; }