NOI 2011 【阿狸的打字機】


之前講了【AC自動姬】,今天我終於把這題給剛下來了。。。嗯,來給大家講一講。

題目描述:

打字機上只有28個按鍵,分別印有26個小寫英文字母和'B'、'P'兩個字母。經阿狸研究發現,這個打字機是這樣工作的:

輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最后)。

按一下印有'B'的按鍵,打字機凹槽中最后一個字母會消失。

按一下印有'P'的按鍵,打字機會在紙上打印出凹槽中現有的所有字母並換行,但凹槽中的字母不會消失。

例如,阿狸輸入aPaPBbP,紙上被打印的字符如下:

a aa ab

我們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現了多少次。

阿狸發現了這個功能以后很興奮,他想寫個程序完成同樣的功能,你能幫助他么?

思路分析:

  四十分算法大家都知道吧?對於每一個詢問都跑KMP就行了。

  正解應該和AC自動機有關,這也應該沒問題吧。

那就給大家講兩個恐怖故事吧:

這道題目,把每一個字符串都存下來會超內存。

這道題目,把每一個字符串都按照原先的方式插入trie樹會超時。

嗯,聽完這兩個恐怖故事是不是瑟瑟發抖呢?(如果沒有,那就********

那么我們先來講一講上面問題的處理方式吧。

看一看阿狸的打字機的運行方式,每次都不會清空,一次只加一個字符,刪除也就刪一個字符,想到了什么?——我們能不能一邊讀入,一邊構造trie樹呢,記錄下每個單詞的最后一個節點,不就可以倒着向上走,還原出各個單詞了嗎?

嗯,非常好,上面的兩個問題就這樣被我們完美解決掉了!(鼓掌

然后我們應該怎么做呢?

觀察詢問——第x個字符串在第y個字符串中出現了幾次。

想一想,在AC自動機上,假如說,一個字符串在另一個字符串中出現會發生什么(假如說是兩個字符串“shehe”和“he”)

(手畫的,有點難看。。。綠色的邊是fail指針)

我們發現,當he在shehe中出現時,出現he的節點3和5,fail指針都指向了7號點he。

這是為什么呢?

回顧我們上篇在強調的——fail指針指向的是當前串的部分后綴與其它模式串的前綴完全相同 的節點。

也就是說,包含he的字符串,肯定可以通過fail指針若干次跳轉,來到he的節點(就是圖中的7號點)。因為,它(指圖中的3、5號點)有部分后綴是和he這個串的前綴完全相同的。

那么我們把所有trie樹的邊去掉,只留下fail指針,這樣也會構成一顆樹對吧,我們叫它fail樹。(如下圖)

也就是說,我們想要求出x字符串在y字符串中出現了幾次,只需要統計fail樹上,有多少屬於y字符串的節點在以x字符串的結束節點為根的子樹里就行了。

樹上統計問題,想到了什么?——DFS序嘛!

因為以x字符串的結束節點為根的子樹在dfs序上是連續的,所以我們肯定不能把它作為突破口,因為它肯定可以在log n的時間內求解(推薦用樹狀數組),沒有必要再去對它進行討論。

把問題進行轉換——統計trie樹上從根到y字符串結束節點的路徑上有多少節點在fail樹上是在以x字符串的結束節點為根的子樹中的。

很容易想到把詢問都離線出來,然后按照y歸類,因為只要y相等的話,那么就可以一次性,把每個詢問在log n的時間內求出來。

這樣,問題就變得簡單了,因為以x字符串的結束節點為根的子樹在dfs序上是連續的,所以我們只要維護好從根到y字符串結束節點的路徑上的節點就可以了。

其實這個東西我們也可以跟着讀入的那一大坨,進行維護。

每加入一個節點就可以在trie樹上走到那個節點,並且在樹狀數組中給它對應的dfn上+1。遇到‘B’時,就在樹狀數組中把之前加的1減掉,這樣我們就可以保證樹狀數組中,只有trie樹上從根到y字符串結束節點的路徑上的節點在fail樹上對應的dfn(貌似有點拗口),是有1的。

代碼實現:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
char st[maxn];
string s[maxn];
vector <int> a[maxn],b[maxn];
int nxt[maxn*2],vet[maxn*2],head[maxn],trie[maxn][30],fail[maxn],his[maxn];
int dfn[maxn],end[maxn],c[maxn],ans[maxn],id[maxn],q[maxn],cnt,tot,tim,n,m,len,now;
void build(){
    int head=0,tail=0;
    for (int i=0;i<26;++i) if (trie[0][i]) q[++tail]=trie[0][i];
    while (head!=tail){
        int now=q[++head];
        for (int i=0;i<26;++i)
            if (trie[now][i]) 
                q[++tail]=trie[now][i],fail[trie[now][i]]=trie[fail[now]][i];
            else trie[now][i]=trie[fail[now]][i];
    }
}
void add(int x,int y){
    ++tot;
    nxt[tot]=head[x];
    vet[tot]=y;
    head[x]=tot;
}
void dfs(int u){
    dfn[u]=++tim;
    for (int i=head[u];i;i=nxt[i]) dfs(vet[i]);
    end[u]=tim;
}
void update(int x,int val){
    if (x<=tim) c[x]+=val,update(x+(x&-x),val);
}
int getsum(int x){
    if (x>0) return c[x]+getsum(x-(x&-x));
    return 0;
}
int main(){
    scanf("%s",st);
    for (int i=0;st[i];i++)
        if (st[i]=='B') now=his[--tot];
        else 
            if (st[i]=='P') id[++n]=now;
            else {
                if (!trie[now][st[i]-'a']) trie[now][st[i]-'a']=++cnt;
                now=trie[now][st[i]-'a']; his[++tot]=now;
            }
    build(); tot=0;
    for (int i=1;i<=cnt;++i) add(fail[i],i);
    scanf("%d",&m); int x,y;
    for (int i=1;i<=m;++i) 
        scanf("%d%d",&x,&y),a[id[y]].push_back(id[x]),b[id[y]].push_back(i);
    dfs(0); int u=0,tot=0;
    memset(his,0,sizeof(his));
    for (int i=0;st[i];i++)
        if (st[i]=='B') update(dfn[u],-1),u=his[--tot];
        else 
            if (st[i]=='P') {
                int siz=a[u].size();
                for (int j=1;j<=siz;j++)
                    ans[b[u][j-1]]=getsum(end[a[u][j-1]])-getsum(dfn[a[u][j-1]]-1);    
            }
            else u=trie[u][st[i]-'a'],his[++tot]=u,update(dfn[u],1);
    for (int i=1;i<=m;++i) printf("%d\n",ans[i]);
    return 0;
}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com