BZOJ2434[Noi2011]阿貍的打字機——AC自動機+dfs序+樹狀數組
阿新 • • 發佈:2018-06-08
IE memset 換行 收藏 就是 namespace fail樹 src 輸入
阿貍發現了這個功能以後很興奮,他想寫個程序完成同樣的功能,你能幫助他麽?
3
1 2
1 3
2 3
1
0
首先講解幾個前置知識點:
1、fail樹;trie樹上每個節點都有且只有一個失配標記,因此把每個節點和它的失配標記連上就形成了一棵樹(這個不用解釋了吧,只有一個失配標記相當於只有一個父親qwq),我們一般稱這棵樹為fail樹。
2、dfs序;這個大家應該都知道,但為了防止有人不知道還是說一下。dfs序就是dfs搜索到的點的順序,在dfs序上一個點的子樹上的所有點都是連著的且都在這個點的後面,也就是每個點的子樹在dfs序上都是一段區間。
每次操作是詢問X串在Y串中出現幾次,也就是詢問Y串上有幾個節點的失配標記直接或間接指向X串的終止節點。有了fail樹也就把問題轉化成了Y串上有多少個節點在以X串終止節點為根的子樹上。
這道題做法比較麻煩,因此分步來講解。
1、首先是讀入的一個字符串,起初我打算模擬棧操作在每次P(打印)時把當前的棧所對應的串插入trie樹,但這樣每次在trie樹上插入字符串都要從根節點重新走,顯然時間復雜度是不行的。所以要直接把讀入字符串插入到trie樹上,當遇到B就回退一個節點,遇到P就給當前節點打一個終止標記,這樣O(串長)就能建出trie樹。
2、找每個節點失配標記並建立fail樹。
3、做fail樹的dfs序並維護每個節點子樹區間的左右端點。
4、讀入每個查詢並掛鏈(查詢比較多)。
5、對trie樹dfs,每到一個節點把這個節點的權值+1,回溯到這個節點把這個點權值-1,(這樣就保證只有當前dfs到的鏈上的點是有值的),當搜索到一個終止節點時,對它所有掛鏈進行進行查詢,對於它的一個查詢的X串,只要在dfs序上把這個X串終止節點的子樹區間用樹狀數組(線段樹也行)求和就行了。但要註意的是,不能真的做回溯dfs,因為這樣會死循環,所以直接按節點編號用循環即可。
最後附上代碼(數組含義在代碼裏寫明)。
題目描述
阿貍喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有28個按鍵,分別印有26個小寫英文字母和‘B‘、‘P‘兩個字母。
經阿貍研究發現,這個打字機是這樣工作的:
l 輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最後)。
l 按一下印有‘B‘的按鍵,打字機凹槽中最後一個字母會消失。
l 按一下印有‘P‘的按鍵,打字機會在紙上打印出凹槽中現有的所有字母並換行,但凹槽中的字母不會消失。
例如,阿貍輸入aPaPBbP,紙上被打印的字符如下:
a
aa
ab
我們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現了多少次。
阿貍發現了這個功能以後很興奮,他想寫個程序完成同樣的功能,你能幫助他麽?
輸入
輸入的第一行包含一個字符串,按阿貍的輸入順序給出所有阿貍輸入的字符。
第二行包含一個整數m,表示詢問個數。
接下來m行描述所有由小鍵盤輸入的詢問。其中第i行包含兩個整數x, y,表示第i個詢問為(x, y)。
輸出
輸出m行,其中第i行包含一個整數,表示第i個詢問的答案。
樣例輸入
aPaPBbP3
1 2
1 3
2 3
樣例輸出
21
0
提示
1<=N<=10^5
1<=M<=10^5 輸入總長<=10^5 這是一道非常好的AC自動機的題(蒟蒻的我調了一下午QAQ),做完這道題相信你能對AC自動機有更深入的理解。1 #include<cmath> 2 #include<queue> 3 #include<vector> 4 #include<cstdio> 5 #include<cstring> 6 #include<cstdlib> 7 #include<iostream> 8 #include<algorithm> 9 using namespace std; 10 queue<int>q; 11 int m; 12 int x,y; 13 int cnt; 14 int tot; 15 int num; 16 int len; 17 int cont; 18 int l[100010];//dfs序上每個數的子樹對應區間的左端點 19 int r[100010];//dfs序上每個數的子樹對應區間的右端點 20 int t[100010];//樹狀數組 21 int f[100010];//每個串的終止節點 22 char s[100010];//讀入字符串 23 int to[100010]; 24 int fa[100010];//trie樹上每個點的父親 25 int ans[100010];//答案 26 int val[100010]; 27 int fail[100010];//失配指針 28 int next[100010]; 29 int head[100010]; 30 int a[100010][26]; 31 void add(int x,int y,int v) 32 { 33 tot++; 34 next[tot]=head[x]; 35 head[x]=tot; 36 to[tot]=y; 37 val[tot]=v; 38 } 39 int ask(int x) 40 { 41 int res=0; 42 for(int i=x;i;i-=i&-i) 43 { 44 res+=t[i]; 45 } 46 return res; 47 } 48 void change(int x,int v) 49 { 50 for(int i=x;i<=num;i+=i&-i) 51 { 52 t[i]+=v; 53 } 54 } 55 void build(char *s)//建立trie樹 56 { 57 int now=0; 58 for(int i=0;i<len;i++) 59 { 60 if(s[i]==‘B‘) 61 { 62 now=fa[now]; 63 } 64 else if(s[i]==‘P‘) 65 { 66 f[++cont]=now; 67 } 68 else 69 { 70 a[now][s[i]-‘a‘]=++cnt; 71 fa[cnt]=now; 72 now=a[now][s[i]-‘a‘]; 73 } 74 } 75 } 76 void getfail()//找每個點失配標記 77 { 78 q.push(0); 79 while(!q.empty()) 80 { 81 int now=q.front(); 82 q.pop(); 83 if(now!=0) 84 { 85 add(fail[now],now,0); 86 } 87 for(int i=0;i<26;i++) 88 { 89 if(a[now][i]!=0) 90 { 91 q.push(a[now][i]); 92 if(now!=0) 93 { 94 fail[a[now][i]]=a[fail[now]][i]; 95 } 96 else 97 { 98 fail[a[now][i]]=0; 99 } 100 } 101 else 102 { 103 a[now][i]=a[fail[now]][i]; 104 } 105 } 106 } 107 } 108 void dfs(int x)//找fail樹的dfs序 109 { 110 l[x]=++num; 111 for(int i=head[x];i;i=next[i]) 112 { 113 dfs(to[i]); 114 } 115 r[x]=num; 116 } 117 int main() 118 { 119 scanf("%s",&s); 120 len=strlen(s); 121 build(s); 122 getfail(); 123 dfs(0); 124 scanf("%d",&m); 125 tot=0; 126 memset(head,0,sizeof(head)); 127 for(int i=1;i<=m;i++) 128 { 129 scanf("%d%d",&x,&y); 130 add(y,x,i); 131 } 132 int now=0; 133 cont=0; 134 for(int i=0;i<len;i++) 135 { 136 if(s[i]-‘a‘==-31) 137 { 138 change(l[now],-1); 139 now=fa[now]; 140 } 141 else if(s[i]-‘a‘==-17) 142 { 143 for(int j=head[++cont];j;j=next[j]) 144 { 145 ans[val[j]]=ask(r[f[to[j]]])-ask(l[f[to[j]]]-1); 146 } 147 } 148 else 149 { 150 now=a[now][s[i]-‘a‘]; 151 change(l[now],1); 152 } 153 } 154 for(int i=1;i<=m;i++) 155 { 156 printf("%d\n",ans[i]); 157 } 158 }View Code
BZOJ2434[Noi2011]阿貍的打字機——AC自動機+dfs序+樹狀數組