【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组
【BZOJ2434】[NOI2011]阿貍的打字機
Description
?阿貍喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有28個按鍵,分別印有26個小寫英文字母和'B'、'P'兩個字母。
經阿貍研究發現,這個打字機是這樣工作的:
l 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最后)。
l 按一下印有'B'的按鍵,打字機凹槽中最后一個字母會消失。
l 按一下印有'P'的按鍵,打字機會在紙上打印出凹槽中現有的所有字母并換行,但凹槽中的字母不會消失。
例如,阿貍輸入aPaPBbP,紙上被打印的字符如下:
a
aa
ab
我們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現了多少次。
阿貍發現了這個功能以后很興奮,他想寫個程序完成同樣的功能,你能幫助他么?
Input
輸入的第一行包含一個字符串,按阿貍的輸入順序給出所有阿貍輸入的字符。 第二行包含一個整數m,表示詢問個數。 接下來m行描述所有由小鍵盤輸入的詢問。其中第i行包含兩個整數x, y,表示第i個詢問為(x, y)。
Output
輸出m行,其中第i行包含一個整數,表示第i個詢問的答案。
Sample Input
aPaPBbP 3 1 2 1 3 2 3Sample Output
2 1 0HINT
1<=N<=10^5 1<=M<=10^5 輸入總長<=10^5題解:先構建fail樹,然后對于一個詢問a b,我們就可以將其轉化成 在以a為根的子樹(這里的子樹指fail樹)中,有多少個節點在單詞b中出現過,于是我們自然想到要用離線處理來搞。就是在DFS遍歷fail樹的時候,分別記錄 從上面查詢到該點 和 從下面回溯到該點 時,有多少個節點在b中出現過,兩者的差就是我們要的答案,我們只需要把 詢問 以 鏈表 的形式存儲到a上就可以了。
但我們發現一個問題,對于某一個節點,它可能存在于很多個單詞之中,那我們搜到這個點時就要把這些單詞出現的次數全部+1,怎么辦?
我們不妨分析一下AC自動機的性質,對于所有包含節點x的單詞,他們的結束點一定在以x為根的子樹中(這里的子樹指AC自動機)。仔細觀察發現,這些結束點在AC自動機的DFS序中一定是一段連續的區間(中間沒有其它結束點)。利用這個性質,我們就可以對于每一個節點x,求出它對應的區間,然后將整個區間+1,這個可以用樹狀數組實現。
(以上都是個人想法,也許麻煩了,但事實證明可以AC)
這么做的惡心之處就在于:1.對于一個結束點,既要知道它在AC自動機中的的編號,又要知道它在詢問中的編號,還要知道它在DFS序中的編號,特別容易搞混。2.DFS要進行兩遍,一遍在AC自動機上搞,一遍在fail樹上搞。3.我們一開始求出來的fail樹是從兒子指向父親的,我們還要正向再建一遍樹。4.一個結束點可能對應多個單詞。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int maxn=100010;
struct node
{int ch[26],fail,l,r,cnt;
}p[maxn];
char str[maxn];
int sta[maxn],tp,s[maxn],pos[maxn];
int n,tot,len,sum,now;
int t1[maxn],n1[maxn],h1[maxn],ans[maxn],n2[maxn],h2[maxn];
int tree[maxn];
queue<int> q;
void updata(int x,int v)
{for(int i=x;i<=sum;i+=i&-i) tree[i]+=v;
}
int query(int x)
{int i,ret=0;for(i=x;i;i-=i&-i) ret+=tree[i];return ret;
}
void dfs1(int x) //遍歷AC自動機
{p[x].l=p[x].r=now; //如果這個點是結束點,那么now就是該點在DFS序中的順序now+=p[x].cnt; //[l,r]就是這個點所影響的單詞的區間for(int i=0;i<26;i++){if(p[x].ch[i]){dfs1(p[x].ch[i]);p[x].r=max(p[x].r,p[p[x].ch[i]].r);}}
}
void build()
{int i,j,u,t;q.push(1);while(!q.empty()){u=q.front(),q.pop();for(i=0;i<26;i++) //這里不要用fail修改兒子了,因為還要DFS {if(!p[u].ch[i]) continue;q.push(p[u].ch[i]);t=p[u].fail;while(!p[t].ch[i]&&t) t=p[t].fail;if(t) p[p[u].ch[i]].fail=p[t].ch[i];else p[p[u].ch[i]].fail=1;}}
}
void dfs(int x) //遍歷fail樹
{int i;for(i=h1[x];i;i=n1[i]){ans[i]-=query(p[t1[i]].l);}updata(p[x].l,1);updata(p[x].r+1,-1);for(i=h2[x];i;i=n2[i]){dfs(i);}for(i=h1[x];i;i=n1[i]) //兩次差值就是答案,意會一下 {ans[i]+=query(p[t1[i]].l);}
}
int main()
{scanf("%s%d",str,&n);len=strlen(str);int i,j,k,u,t,a,b;u=sta[++tp]=1;tot=1;for(i=0;i<len;i++) //用棧搞一搞,輸入其實很簡單 {if(str[i]=='B') u=sta[--tp];else if(str[i]=='P') p[u].cnt++,pos[++sum]=u;else{if(!p[u].ch[str[i]-'a']) p[u].ch[str[i]-'a']=++tot;u=p[u].ch[str[i]-'a'];sta[++tp]=u;}}for(i=1;i<=n;i++){scanf("%d%d",&a,&b); //將詢問存儲到a節點上 t1[i]=pos[b];n1[i]=h1[pos[a]];h1[pos[a]]=i;}build();now=1;dfs1(1);for(i=2;i<=tot;i++) //重建fail樹 {n2[i]=h2[p[i].fail];h2[p[i].fail]=i;}dfs(1);for(i=1;i<=n;i++) printf("%d\n",ans[i]);return 0;
} ?
轉載于:https://www.cnblogs.com/CQzhangyu/p/6262165.html
總結
以上是生活随笔為你收集整理的【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组的全部內容,希望文章能夠幫你解決所遇到的問題。