题解 P4770 【[NOI2018]你的名字】

zhengrunzhe

2019-07-20 10:01:52

Solution

题解里好像都是线段树合并 这里提供一个转化为二维偏序问题然后只用一棵线段树维护的做法 感谢**E_Space**在FJ冬令营的讲解 后缀自动机的**匹配**:**找T的所有前缀中最长的是S子串的后缀** **双指针扫描法**: “比如对于T的第i个前缀,匹配的状态为x 设c为T的第i+1个字符 如果xc是S的子串,那么显然T的第i+1个前缀最长的(是S的子串的)后缀是xc 这同时意味着x所在节点有关于c的转移边 于是你只需要将x沿着这条边走一步,再将其长度加上1即可 否则说明T的第i+1个前缀最长的(是S的子串的)后缀比xc短 考虑不断删去xc的首字符,直到它是S的子串为止 这也相当于**不断删去x的首字符,直到它有c的转移边为止**,当然要注意特判删到空以后仍然没有c的转移边的情况 在删首字符的时候注意判断是否要跳到所在节点的父亲,当然你也可以把删去首字符改成**直接跳父亲并修改当前串长度为父亲的len**来节省常数” ——摘自Espace课件 弱化版问题: **求S和T有多少个本质不同的非空公共子串** 考虑枚举T的每个本质不同的子串询问其是否是S的子串 “先建出T的后缀自动机,然后考虑每个节点接受的字符串中有哪些是S的子串 由于一个节点接受的字符串都是某个子串的后缀,所以我们可以找到这些字符串在T中的一个右端点(即节点的at),然后查询一下T的长度为at的前缀最长的(是S的子串的)后缀的长度len 这样我们就可以知道T的以at为右端点的子串中长度不超过len的都是S的子串,长度大于len的都不是S的子串 于是我们就可以把(0,len]和节点接受字符串长度的区间(即(父亲的max,自己的max])的交中的整数个数计入答案 至于如何求len,只要预处理的时候建出S的自动机,用T在上面跑一遍就可以了” ——摘自E_Space课件 ### [NOI2018]你的名字: **给定S(Len(S)<=5e5),Q(Q<=1e5)次询问(T,l,r)求T(∑Len(T)<=1e6)和S[l,r]有多少个本质不同的非空子串** “仍然考虑之前的双指针扫描法,从Lj开始,不断删去其首字符,直到它加上T的第j+1位是S[li:ri]的子串 原先我们的判断方法是判断当前状态所在的节点是否有T(j+1)的转移边 但是现在不能这么判了,因为**转移边指向的状态(如果有的话)是S的子串但不一定是S[li:ri]的子串** 我们的问题是,如何判断一个状态x是否在S[li:ri]中出现 考虑x在S中出现的所有(右端点的)位置,即x所在节点的Right集合 Right集合中什么样的位置p才会让x在S[li:ri]中出现呢? x在S[li:ri]中出现当且仅当x所在节点的Right集合中存在p使得 **p≤ri 并且 p-len(x)+1≥li**(其中len(x)是x的长度) 那么就是**判断Right集合中不超过ri的p中最大的(p-len(x)+1)是否大于等于li** 当然,(p-len(x)+1)最大当且仅当p最大 这是个经典的**二维偏序**问题 一维是**p≤ri**,另一维是at值为p的节点必须要在x所在节点的子树中(这样Right集合中才会有p)” 对于第一维:把所有询问存下来,离线处理,按照r从小到大排序,然后把S的后缀自动机中所有at<=r的np节点激活,插入到线段树中 对于第二维:建S后缀自动机后 我们把所有节点的fa和它自己连边,形成一棵树,利用树的**dfs序**,我们能将树的一棵子树范围转化成一段区间,从而用线段树求区间最大值 时间复杂度O(∑Len(T) log Len(S)) ```cpp #include<cstdio> #include<cstring> #include<algorithm> using std::max; using std::sort; typedef long long ll; const int LS=5e5+10,LT=1e6+10,Q=1e5+10; template<int maxn>struct Suffix_Automaton { int son[maxn][26],fa[maxn],back[maxn],right[maxn],len[maxn],last,cnt; inline const void clear(int p) { fa[p]=right[p]=back[p]=len[p]=0; memset(son[p],0,sizeof(son[p])); } inline const void insert(int c,int k) { int p=last,np=++cnt;clear(np); len[last=np]=len[p]+1;back[right[np]=k]=np; for (;p&&!son[p][c];p=fa[p])son[p][c]=np; if (!p)return (void)(fa[np]=1); int q=son[p][c]; if (len[q]==len[p]+1)return (void)(fa[np]=q); int nq=++cnt;len[nq]=len[p]+1; memcpy(son[nq],son[q],sizeof(son[q]));fa[nq]=fa[q]; fa[q]=fa[np]=nq;right[nq]=k; for (;p&&son[p][c]==q;p=fa[p])son[p][c]=nq; } inline const void build(char *s) { int n=strlen(s+1); for (int i=1;i<=n;i++)insert(s[i]-97,i); } inline const void clear() { clear(last=cnt=1); } inline Suffix_Automaton(){last=cnt=1;} }; Suffix_Automaton<LS<<1>S,T; int to[LS<<1],next[LS<<1],head[LS<<1],cnt; inline const void addedge(int u,int v) { next[++cnt]=head[u];to[head[u]=cnt]=v; } inline const void build() { for (int i=1;i<=S.cnt;i++)if (S.fa[i])addedge(S.fa[i],i); } int dfn[LS<<1],end[LS<<1]; inline const void dfs(int p) { dfn[p]=++cnt; for (int i=head[p];i;i=next[i])dfs(to[i]); end[p]=cnt; } class Segment_Tree { private: struct tree { int mx; tree *lson,*rson; inline const void pushup() { mx=max(lson->mx,rson->mx); } inline const void update(int l,int r,int pos,int key) { if (l==r)return (void)(mx=key); int mid=l+r>>1; if (pos<=mid)lson->update(l,mid,pos,key); else rson->update(mid+1,r,pos,key); pushup(); } inline const int query(int l,int r,int L,int R) { if (l>R||r<L)return 0; if (l>=L&&r<=R)return mx; int mid=l+r>>1; return max(lson->query(l,mid,L,R),rson->query(mid+1,r,L,R)); } }*root,memory_pool[LS<<3],*tail; inline const void build(tree *&p,int l,int r) { p=tail++; if (l==r)return; int mid=l+r>>1; build(p->lson,l,mid); build(p->rson,mid+1,r); } public: inline Segment_Tree(){tail=memory_pool;} inline const void build(){build(root,1,S.cnt);} inline const void update(int pos,int key){root->update(1,S.cnt,pos,key);} inline const int query(int l,int r){return root->query(1,S.cnt,l,r);} }sgt; char s[LS],t[LT],*tail=t; ll ans[Q]; int m; struct query { char *t;int l,r,len,id; inline query(char *a=0,int b=0,int c=0,int d=0,int e=0):t(a),l(b),r(c),len(d),id(e){} inline const bool operator<(const query &q)const { return r<q.r; } }q[Q]; int f[LT]; int main() { scanf("%s",s+1);S.build(s); build();cnt=0;dfs(1);sgt.build(); scanf("%d",&m); for (int i=1;i<=m;i++) { int l,r; scanf("%s%d%d",tail+1,&l,&r); q[i]=query(tail,l,r,strlen(tail+1),i); tail+=q[i].len+1; } sort(q+1,q+m+1);cnt=1; for (int i=1;i<=m;i++) { for (;cnt<=q[i].r;cnt++)sgt.update(dfn[S.back[cnt]],cnt); T.build(q[i].t); int now=1,len=0; for (int j=1;j<=q[i].len;j++) { while (1) { if (!now){len=0;now=1;break;} int nxt=S.son[now][q[i].t[j]-97]; if (!nxt)if (now=S.fa[now],now)len=S.len[now];else; else {now=nxt;len++;break;} } while (1) { int mx=sgt.query(dfn[now],end[now]); if (mx&&mx-len+1>=q[i].l)break; if (mx&&mx-S.len[S.fa[now]]+1>q[i].l){len=mx-q[i].l+1;break;} now=S.fa[now];len=S.len[now]; } f[j]=len; } ll nowans=0ll; for (int j=2;j<=T.cnt;j++) { int right=T.right[j],len=T.len[j],flen=T.len[T.fa[j]]; if (f[right]<=flen)nowans+=1ll*len-flen; else if (f[right]<len)nowans+=len-f[right]; } ans[q[i].id]=nowans; T.clear(); } for (int i=1;i<=m;i++)printf("%lld\n",ans[i]); return 0; } ```