1. 程式人生 > >BZOJ3881 [Coci2015]Divljak題解(AC自動機+dfs序+樹鏈的並+LCA+樹上差分+樹狀陣列)

BZOJ3881 [Coci2015]Divljak題解(AC自動機+dfs序+樹鏈的並+LCA+樹上差分+樹狀陣列)

題目:BZOJ3881.
題目大意:Alice有n個字串 S 1 , S 2 . .

. S n S_1,S_2...S_n ,Bob有一個字串集合T,一開始集合為空.有q個操作,操作有兩種形式:
“1 P”,Bob往自己的集合裡添加了一個字串P.
“2 x”,Alice詢問Bob,集合T中有多少個字串包含串 S
x S_x
.
(原題太精簡了我不知道怎麼樣繼續精簡了)

首先考慮對S構建出AC自動機,那麼加入一個串後考慮T串對AC自動機上每一個點的貢獻(要麼是1要麼是0).

我們發現當T串在某位置匹配上AC自動機上的一個點時,那麼這個點在fail樹上的所有祖先都能夠被匹配上,這讓我們想到把T串放入AC自動機中暴力跳fail指標來染色,但明顯這樣做沒有效率保證會TLE.

這個時候我們新增一個概念叫做樹鏈的並,樹鏈的並就是在樹上的n條以根為一端的鏈將這些鏈之間經過的所有點都染上色.很容易想到這個東西可以暴力染色做到 O

( n 2 ) O(n^2) ,但它其實有更優秀的 O ( n l o g n ) O(nlogn) 做法.

我們考慮對n條鏈按照根以外的另一端的dfs序排序,然後就可以在將n條鏈上的每一個點加1,並在相鄰兩個節點的LCA處到根這一條鏈減1,顯然這樣做是正確的,不明白的可以對著下圖yy一下:
在這裡插入圖片描述
比如像我們要染色三條鏈分別以4,5,3為另一個端點,這樣做就是很正確的(我知道這個例圖不是很優秀 ).

明白了樹鏈的並後,我們發現這樣做還是不能快速修改,所以考慮將所有資訊樹上差分一下,然後查詢就變成查詢子樹和了,樹狀陣列就可以解決.

這個時間複雜度看起來並不能過掉 2 1 0 6 2*10^6 級別的資料,但是由於我們用到的幾個帶log的演算法常數都很小,所以這樣做還是可以過的.

程式碼如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;
#define m(a) memset(a,0,sizeof(a))

const int N=2000000,C=26;

struct Trie{
  int s[C],fail;
  Trie(){m(s);fail=0;}
}tr[N+9];
int cn,h[N+9];

void Trie_build(){tr[cn=0]=Trie();}

void Trie_insert(char *c,int len,int id){
  int x=0;
  for (int i=1;i<=len;++i)
    if (tr[x].s[c[i]-'a']) x=tr[x].s[c[i]-'a'];
    else {
      tr[x].s[c[i]-'a']=++cn;
      tr[x=cn]=Trie();
    }
  h[id]=x;
}

queue<int>q;

void Fail_get(){
  for (int i=0;i<C;++i)
    if (tr[0].s[i]) q.push(tr[0].s[i]);
  while (!q.empty()){
    int x=q.front(),t;q.pop();
    for (int i=0;i<C;++i)
      if (tr[x].s[i]) tr[tr[x].s[i]].fail=tr[tr[x].fail].s[i],q.push(tr[x].s[i]);
      else tr[x].s[i]=tr[tr[x].fail].s[i];
  }
}

struct tree{
  int y,next;
}e[N+9];
int lin[N+9],top;

void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}

void Fail_build(){
  for (int i=1;i<=cn;++i)
    ins(tr[i].fail,i);
}

struct node{
  int deep,fa,siz,son,top,dfn,low;
}d[N+9];

void Fail_dfs1(int k,int fa){
  d[k].fa=fa;
  d[k].deep=d[fa].deep+1;
  d[k].siz=1;
  d[k].son=cn+1;
  for (int i=lin[k];i;i=e[i].next){
  	Fail_dfs1(e[i].y,k);
  	d[k].siz+=d[e[i].y].siz;
  	if (d[d[k].son].siz<d[e[i].y].siz) d[k].son=e[i].y;
  }
}

int cdfs;

void Fail_dfs2(int k,int top){
  d[k].dfn=++cdfs;
  d[k].top=top;
  if (d[k].son^cn+1) Fail_dfs2(d[k].son,top);
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^d[k].son) Fail_dfs2(e[i].y,e[i].y);
  d[k].low=cdfs; 
}

int Fail_lca(int x,int y){
  while (d[x].top^d[y].top)
  	d[d[x].top].deep>d[d[y].top].deep?x=d[d[x].top].fa:y=d[d[y].top].fa;
  return d[x].deep<d[y].deep?x:y;
}

int c[N+9];

void Bit_add(int x,int v){for (;x<=cn+1;x+=x&-x) c[x]+=v;}
int Bit_query(int x){int sum=0;for (;x;x-=x&-x) sum+=c[x];return sum;}
void Fail_add(int x,int v){Bit_add(d[x].dfn,v);}
int Fail_query(int x){return Bit_query(d[x].low)-Bit_query(d[x].dfn-1);}

int tmp[N+9];

bool cmp(const int &a,const int &b){return d[a].dfn<d[b].dfn;}

void Change(char *c,int len){
  int x=0;
  for (int i=1;i<=len;++i){
  	x=tr[x].s[c[i]-'a'];
  	Fail_add(x,1);
  	tmp[i]=x; 
  }
  sort(tmp+1,tmp+1+len,cmp);
  for (int i=1;i<len;++i)
    Fail_add(Fail_lca(tmp[i],tmp[i+1]),-1);
}

int n,len;
char s[N+9];

Abigail into(){
  scanf("%d",&n);
  Trie_build();
  for (int i=1;i<=n;++i){
  	scanf("%s",s+1);
  	len=strlen(s+1);
  	Trie_insert(s,len,i);
  }
}

Abigail work(){
  Fail_get();
  Fail_build();
  Fail_dfs1(0,cn+1);
  Fail_dfs2(0,0);
}

Abigail getans(){
  int q,opt,x;
  scanf("%d",&q);
  for (int i=1;i<=q;++i){
  	scanf("%d",&opt);
  	if (opt==1){
  	  scanf("%s",s+1);
  	  len=strlen(s+1);
  	  Change(s,len);
  	}else{
  	  scanf("%d",&x);
  	  printf("%d\n",Fail_query(h[x])); 
  	}
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
}