[NOI2018]你的名字

description

luogu
给定长为(n)的文本串(S)(Q)个模式串(T),每次询问不在(S[l,r])中出现的(T)的本质不同子串个数。
对于前(68\%)的数据有(l=1,r=|S|)

solution

我会(l=1,r=|S|)+单次询问!
建出(S),(T)(SAM),算出两个串各自的本质不同子串个数;
然后把(T)(S)(SAM)上跑一遍,(dfs)得到每个节点的最长匹配长度,最后把每个节点相加就可以得到(T)(S)中出现的本质不同子串个数,减掉即可。
这样的时间复杂度很高,为(O(Q(|S|+|T_i|))),只能通过原题(28\%)的数据。

(S)(SAM)上做肯定会(TLE),我们看到(T)(SAM),对于每个节点,考虑其和(S)重复的部分,这一定是(T)的某个前缀(i)的一段长度连续的后缀。
进而我们知道只要前缀(i)的某一个后缀与(S)重复,那么前缀(i)小于这个后缀长度的所有后缀一定也和(S)重复。
那么对于(T)(SAM)中的每一个节点,我们要考虑其对答案的贡献,假设这个节点(endpos)集合的其中一个为(i),那么只要根据前缀(i)的最长匹配长度(len_i)进行判断即可。
数组(len[])的长度为(|T|),这个我们可以通过之前在(S)(SAM)上跑一遍的方法得到。
于是总复杂度降为了(O(|S|+sum|T|)),可以通过原题(68\%)的数据。

最后考虑如果(S)变成了(S[l,r])应该如何去做。
直接按照(l=1,r=|S|)去做出现的唯一问题就是(len_i)不一定是正确的。
也就是说现在我们需要正确得到在(S[l,r])上进行匹配的(len_i)
考虑线段树合并维护每个节点的(endpos)集合,那么我们在(SAM)上转移时,只要满足转移后的节点存在合法的(endpos)即可转移。
存在合法的(endpos)指对于转移后的(len_i),有(T)的前缀(i)的长为(len_i)的后缀在(S[l,r])内。
但我们无法提前知道这个(len_i),因此需要通过二分(或者暴(for))来得到最终答案。

code

震惊!NOI2018D1T3代码竟不到100行

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
inline ll read(){
  ll data=0,w=1;char ch=getchar();
  while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
  if(ch=='-')w=-1,ch=getchar();
  while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
  return data*w;
}
int n,m,Q,res[N];char S[N],T[N];
int head[N],nxt[N],to[N],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int rt[N],s[2][100*N],tot;
#define mid ((l+r)>>1)
void insert(int &i,int l,int r,int p){
  i=++tot;s[0][i]=s[1][i]=0;if(l==r)return;
  p<=mid?insert(s[0][i],l,mid,p):insert(s[1][i],mid+1,r,p);
}
int merge(int a,int b){
  if(!a||!b)return a|b;int c=++tot;
  s[0][c]=merge(s[0][a],s[0][b]);s[1][c]=merge(s[1][a],s[1][b]);return c;
}
int query(int i,int l,int r,int x,int y){
  if(!i||y<l||r<x||x>y)return 0;if(x<=l&&r<=y)return 1;
  return query(s[0][i],l,mid,x,y)|query(s[1][i],mid+1,r,x,y);
}
struct SAM{
  int lst,tot,tr[N][26],len[N],fa[N],pos[N],cntp;
  inline void clear(){
    for(int i=1;i<=tot;i++)
      len[i]=fa[i]=pos[i]=0,memset(tr[i],0,sizeof(tr[i]));
    lst=tot=1;cntp=0;
  }
  inline void extend(int c){
    int cur=++tot,u=lst;len[cur]=len[lst]+1;
    while(u&&!tr[u][c])tr[u][c]=cur,u=fa[u];
    if(!u)fa[cur]=1;
    else{
      int v=tr[u][c];
      if(len[v]==len[u]+1)fa[cur]=v;
      else{
    int clone=++tot;len[clone]=len[u]+1;pos[clone]=pos[v];
    memcpy(tr[clone],tr[v],sizeof(tr[clone]));
    fa[clone]=fa[v];fa[cur]=fa[v]=clone;
    while(u&&tr[u][c]==v)tr[u][c]=clone,u=fa[u];
      }
    }
    lst=cur;pos[cur]=++cntp;
  }
  void dfs(int u){
    if(pos[u])insert(rt[u],1,n,pos[u]);
    for(int i=head[u],v;i;i=nxt[i])dfs(v=to[i]),rt[u]=merge(rt[u],rt[v]);
  }
  inline void work(){for(int i=2;i<=tot;i++)add(fa[i],i);dfs(1);}
  inline ll calc(int *res){
    ll ans=0;
    for(int i=1;i<=tot;i++)ans+=max(len[i]-max(len[fa[i]],res[pos[i]]),0);
    return ans;
  }
  inline void run(char *T,int m,int x,int y,int *res){
    for(int i=1,c,u=1,l=0,v,L,R,M,AS;i<=m;i++){
      c=T[i]-'a';
      while(1){
	if(!u){u=1;l=0;break;}
	if(!tr[u][c]){u=fa[u];l=min(l,len[u]);continue;}
	v=tr[u][c];L=len[fa[v]]+1;R=l+1;AS=-1;
	while(L<=R){
	  M=(L+R)>>1;
	  if(query(rt[v],1,n,x+M-1,y))
	    AS=M,L=M+1;
	  else R=M-1;
	}
	if(AS==-1){u=fa[u];l=min(l,len[u]);}
	else{u=v;l=AS;break;}
      } 
      res[i]=l;
    }
  }
}samS,samT;
int main()
{
  scanf("%s",S+1);n=strlen(S+1);samS.clear();samT.clear();
  for(int i=1;i<=n;i++)samS.extend(S[i]-'a');samS.work();Q=read();
  while(Q--){
    scanf("%s",T+1);m=strlen(T+1);samT.clear();int l=read(),r=read();
    for(int i=1;i<=m;i++)samT.extend(T[i]-'a');
    samS.run(T,m,l,r,res);printf("%lld
",samT.calc(res));
  }
  return 0;
}
原文地址:https://www.cnblogs.com/cjfdf/p/10263380.html