CF 666E Forensic Examination——广义后缀自动机+线段树合并

题目:http://codeforces.com/contest/666/problem/E

对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问。

还要处理模式串的区间,可以用线段树。给广义自动机的每个节点开一棵线段树存该节点代表的串在各模式串中的出现情况。

线段树合并到叶子时,直接把出现次数相加。这样会改值,所以如果不新建节点的话,父亲用的孩子的节点,父亲又要改值,在孩子上查询的时候就错了。

  可以每次不是 ( !cr || !pr ) 的时候都新建节点。或者把询问离线挂在自动机节点上,准备把该节点的线段树合并给父亲的时候把该节点的询问先查询掉。

定位询问子串可以倍增。先走一遍得到询问串每个前缀最长匹配到哪个节点,查询子串的时候从该子串右端点对应的节点 cr 开始跳 fa 直到 len[ cr ] >= d && len[ fa ] < d (d 是询问子串长度)。跳的过程可以倍增。

注意倍增边界是 K2 而非 K 。注意数组大小 +5 。

需要判断一下询问子串没在自动机里出现。就是右端点对应的节点的匹配长度都 < d (此时没跳 fa ,是该右端点可能的最大匹配长度)。注意是 “匹配长度” 而不是该节点的 len 。

注意若没出现,返回的应是 ( L , 0 ) 。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define pb push_back
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
const int N=5e5+5,M=1e5+5,K=26,K2=17,M2=M*K2;
int n,m,Q,cnt=1,fa[M],len[M],go[M][K+5],pre[M][K2+5],ps[N],ct[N];//K2+5!!
int c[N],tx[N],rt[M],tot,ls[M2],rs[M2];
char s[N],t[N];
struct Ques{
  int l,r,id;
  Ques(int l=0,int r=0,int i=0):l(l),r(r),id(i) {}
}q[N];
struct Node{
  int mx,sm;
  Node(int m=0,int s=0):mx(m),sm(s) {}
  Node operator+ (const Node &b)const
  { if(sm>=b.sm)return *this; return b;}
}vl[M2],ans[N];
vector<Ques> vt[N];
int cz(int p,int w)
{
  int q=go[p][w], nq=++cnt; len[nq]=len[p]+1;
  fa[nq]=fa[q]; fa[q]=nq;
  memcpy(go[nq],go[q],sizeof go[q]);
  for(;go[p][w]==q;p=fa[p])go[p][w]=nq;
  return nq;
}
int ins(int p,int w)
{
  if(go[p][w])
    {
      int q=go[p][w];
      if(len[q]==len[p]+1)return q;
      return cz(p,w);
    }
  int np=++cnt; len[np]=len[p]+1;
  for(;p&&!go[p][w];p=fa[p])go[p][w]=np;
  if(!p){fa[np]=1;return np;}
  int q=go[p][w]; if(len[q]==len[p]+1)fa[np]=q;
  else fa[np]=cz(p,w); return np;
}
void add(int l,int r,int &cr,int p)
{
  if(!cr)cr=++tot; if(l==r){vl[cr]=Node(l,1); return;}
  int mid=l+r>>1;
  if(p<=mid)add(l,mid,ls[cr],p);
  else add(mid+1,r,rs[cr],p);
  vl[cr]=vl[ls[cr]]+vl[rs[cr]];
}
void mrg(int l,int r,int &cr,int pr)
{
  if(!cr||!pr){if(!cr)cr=pr;return;}
  if(l==r){vl[cr].sm+=vl[pr].sm; return;}
  int mid=l+r>>1;
  mrg(l,mid,ls[cr],ls[pr]);
  mrg(mid+1,r,rs[cr],rs[pr]);
  vl[cr]=vl[ls[cr]]+vl[rs[cr]];
}
Node qry(int l,int r,int cr,int L,int R)
{
  if(!cr)return Node(L,0);///!!
  if(l>=L&&r<=R)return vl[cr];
  int mid=l+r>>1; Node ret=Node(L,0);//L
  if(L<=mid)ret=qry(l,mid,ls[cr],L,R);
  if(mid<R)ret=(ret+qry(mid+1,r,rs[cr],L,R));
  return ret;
}
void Rsort()
{
  for(int i=1;i<=cnt;i++)tx[len[i]]++;
  for(int i=1;i<=cnt;i++)tx[i]+=tx[i-1];//0 for root
  for(int i=1;i<=cnt;i++)c[tx[len[i]]--]=i;
}
int main()
{
  scanf("%s",s+1); n=strlen(s+1);
  m=rdn();
  for(int i=1;i<=m;i++)
    {
      scanf("%s",t+1); int d=strlen(t+1);
      for(int j=1,p=1;j<=d;j++)
    p=ins(p,t[j]-'a'+1), add(1,m,rt[p],i);
    }
  Rsort();
  for(int i=1;i<=cnt;i++)
    {
      int cr=c[i];
      pre[cr][0]=fa[cr];
      for(int t=1,d=fa[cr];(d=pre[d][t-1]);t++)
    pre[cr][t]=d;
    }
  Q=rdn(); int cr=1;
  for(int i=1,c2=0;i<=n;i++)
    {
      int w=s[i]-'a'+1;
      while(cr&&!go[cr][w])cr=fa[cr],c2=len[cr];
      if(!go[cr][w])cr=1, c2=0;
      else cr=go[cr][w], c2++;
      ps[i]=cr;ct[i]=c2;
    }
  for(int i=1,l,r,ql,qr;i<=Q;i++)
    {
      l=rdn();r=rdn();ql=rdn();qr=rdn();
      int cr=ps[qr],c2=ct[qr],d=qr-ql+1;
      if(c2<d){ans[i]=Node(l,0);continue;}//l not 1
      for(int t=K2;t>=0;t--)//K2!!
    if(len[pre[cr][t]]>=d) cr=pre[cr][t];
      vt[cr].pb(Ques(l,r,i));
    }
  for(int i=cnt;i>1;i--)
    {
      int cr=c[i];
      for(int j=0,lm=vt[cr].size();j<lm;j++)
    {
      Ques q=vt[cr][j];
      ans[q.id]=qry(1,m,rt[cr],q.l,q.r);
    }
      mrg(1,m,rt[fa[cr]],rt[cr]);
    }
  for(int i=1;i<=Q;i++)printf("%d %d\n",ans[i].mx,ans[i].sm);
  return 0;
}

 

上一篇:ASM案例 - 如何判断一个类是枚举类


下一篇:批处理编程案例