HDU 2243考研路茫茫——单词情结 (AC自动机+矩阵快速幂)
生活随笔
收集整理的這篇文章主要介紹了
HDU 2243考研路茫茫——单词情结 (AC自动机+矩阵快速幂)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
背單詞,始終是復習英語的重要環節。在荒廢了3年大學生涯后,Lele也終于要開始背單詞了。
一天,Lele在某本單詞書上看到了一個根據詞根來背單詞的方法。比如"ab",放在單詞前一般表示"相反,變壞,離去"等。
于是Lele想,如果背了N個詞根,那這些詞根到底會不會在單詞里出現呢。更確切的描述是:長度不超過L,只由小寫字母組成的,至少包含一個詞根的單詞,一共可能有多少個呢?這里就不考慮單詞是否有實際意義。
比如一共有2個詞根 aa 和 ab ,則可能存在104個長度不超過3的單詞,分別為
(2個) aa,ab,
(26個)aaa,aab,aac...aaz,
(26個)aba,abb,abc...abz,
(25個)baa,caa,daa...zaa,
(25個)bab,cab,dab...zab。
這個只是很小的情況。而對于其他復雜點的情況,Lele實在是數不出來了,現在就請你幫幫他。
思路:題目要求不超過L的所有符合單詞,我們先考慮等于L
對于所有滿足的情況 = 所有情況-不滿足情況
不滿足情況即是不包含詞根的單詞,對于計算滿足長度所有情況,可以聯想到矩陣快速冪,這樣我們就只需要建一個矩陣圖
對于建圖,我們知道,所有以詞根作為前綴子串的都是不符合條件的。
從根開始記錄每種前綴可以添加的字母路徑數(字母不是詞根的結尾)。
于是我們可以想到AC自動機,對于自動機補全的trie圖,每個單詞的fail都指向其最長子后綴
也就是從根以fail指針向下建圖的話,當遍歷到一個詞根末尾時,其下面的有詞根都時不合法的,這可以用dfs維護。
我們知道當矩陣中存有邊權(代表路徑數)時(相當于到每個點走一條路徑),我們將自身乘自身,得到的二次冪矩陣,每個位置的權值就相當于從i走到j走兩條路徑的路徑數目。(離散數學)
這樣推廣到這,就相當于算出矩陣的L次冪,其第一行的和就是從0長度到L長度經過各個路徑以各種字母結尾的路徑和,這是長度等于L的情況。
那么對于長度<=L的所有情況,我們知道?Σ(A1+A2+A3+...+AL),然后其第一行的和即是答案。
一開始寫的是遞歸,對于L=6為偶數,A1+A2+A3+A3(A1+A2+A3)
對于L=5為奇數,A1+A2+A2(A1+A2)+A5
但也許是姿勢不對,一直tle。
然后看了Discus,我們可以添加一維,(假設原本矩陣為L,此L!=題面的L),使得L+1列全是1,這樣對于第L個矩陣maps【1】【L+1】就儲存Σ(A1+A2+A3+...+A[L-1])
對于取模,因為是對1<<64取模,太大了直接取不了(睿智的嘗試),可以直接把相關數組開成unsign long long 可以直接對溢出舍去相當于取模
還有利用等比數列公式時,由于涉及除法,利用歐拉定理求出逆元即可。
附上丑代碼 1 #include<bits/stdc++.h> 2 using namespace std; 3 4 typedef unsigned long long ull; 5 struct Node 6 { 7 int y,next; 8 } node[41]; 9 int cnt,head[40],L; 10 int n; 11 ull len; 12 int tohash[40],topos; 13 void add(int x,int y) 14 { 15 node[++cnt].y=y; 16 node[cnt].next=head[x]; 17 head[x]=cnt; 18 } 19 20 struct MAP 21 { 22 ull m[40][40]; 23 friend MAP operator*(const MAP a,const MAP b) 24 { 25 MAP tmp; 26 for(int i=1; i<=L+1; i++) 27 { 28 for(int j=1; j<=L+1; j++) 29 { 30 tmp.m[i][j] = 0; 31 32 for(int k=1; k<=L+1; k++) 33 { 34 tmp.m[i][j] = tmp.m[i][j]+ a.m[i][k]*b.m[k][j]; 35 } 36 37 } 38 } 39 return tmp; 40 } 41 // friend MAP operator+(const MAP a,const MAP b) 42 // { 43 // MAP tmp; 44 // for(int i=1;i<=L;i++) 45 // { 46 // for(int j=1;j<=L+;j++) 47 // { 48 // tmp.m[i][j] = a.m[i][j] + b.m[i][j]; 49 // } 50 // } 51 // return tmp; 52 // } 53 void init() 54 { 55 for(int i=1; i<=L+1; i++) 56 { 57 for(int j=1; j<=L+1; j++) 58 { 59 m[i][j] = 0; 60 } 61 m[i][i] = 1; 62 } 63 } 64 } maps; 65 struct ACTO 66 { 67 int trie[40][26],tot; 68 int dp[40]; 69 int fail[40]; 70 int End[40]; 71 void init() 72 { 73 tot = cnt = L = topos = 0; 74 memset(trie,0,sizeof(trie)); 75 memset(maps.m,0,sizeof(maps.m)); 76 memset(head,0,sizeof(head)); 77 memset(dp,0,sizeof(dp)); 78 memset(fail,0,sizeof(fail)); 79 memset(End,0,sizeof(End)); 80 memset(tohash,0,sizeof(tohash)); 81 } 82 void Insert(char *str,int num) 83 { 84 int len = strlen(str),p=0; 85 for(int i=0; i<len; i++) 86 { 87 int ch = str[i]-'a'; 88 if(!trie[p][ch])trie[p][ch]=++tot; 89 p = trie[p][ch]; 90 } 91 End[p]=num; 92 } 93 void get_fail() 94 { 95 queue<int>que; 96 while(!que.empty())que.pop(); 97 for(int i=0; i<26; i++)if(trie[0][i])que.push(trie[0][i]); 98 while(!que.empty()) 99 { 100 int p = que.front(); 101 que.pop(); 102 for(int i=0; i<26; i++) 103 { 104 if(trie[p][i]) 105 { 106 fail[trie[p][i]] = trie[fail[p]][i]; 107 que.push(trie[p][i]); 108 } 109 else trie[p][i] = trie[fail[p]][i]; 110 } 111 } 112 } 113 void make_edge() 114 { 115 for(int i=1; i<=tot; i++) 116 { 117 add(fail[i],i); 118 } 119 } 120 void dfs(int x,int f) 121 { 122 if(End[x])f=1; 123 dp[x] = f; 124 for(int i=head[x]; i; i=node[i].next) 125 { 126 dfs(node[i].y,f); 127 } 128 129 } 130 void graph() 131 { 132 for(int i=0; i<=tot; i++) 133 { 134 if(!dp[i]) 135 { 136 L++; 137 if(!tohash[i])tohash[i] = ++topos; 138 for(int j=0; j<26; j++) 139 { 140 if(!dp[trie[i][j]]) 141 { 142 if(!tohash[trie[i][j]])tohash[trie[i][j]]=++topos; 143 maps.m[tohash[i]][tohash[trie[i][j]]]++; 144 } 145 } 146 } 147 } 148 } 149 }; 150 151 MAP qpow(MAP a,ull b) 152 { 153 MAP ans; 154 ans.init(); 155 MAP base = a; 156 while(b) 157 { 158 if(b&1)ans = ans*base; 159 base = base * base; 160 b >>= 1; 161 } 162 return ans; 163 } 164 165 ACTO AC; 166 167 //MAP cal(int l,int r) 168 //{ 169 // 170 // if(l==r)return maps; 171 // int mid = r/2; 172 // if(r & 1) 173 // { 174 // return cal(l,mid)+qpow(maps,mid)*cal(l,mid)+qpow(maps,r); 175 // } 176 // else 177 // { 178 // return cal(l,mid)+cal(l,mid)*qpow(maps,mid); 179 // } 180 //} 181 182 183 184 ull qmulti(ull m,ull n) 185 { 186 ull ans = 0; 187 while(n) 188 { 189 if(n&1)ans += m; 190 m += m; 191 n >>= 1; 192 } 193 return ans; 194 } 195 ull qpow(ull a,ull b) 196 { 197 ull ans = 1; 198 ull base = a; 199 while(b) 200 { 201 if(b&1)ans = qmulti(ans,base); 202 base = qmulti(base,base); 203 b >>= 1; 204 } 205 return ans; 206 } 207 208 int main() 209 { 210 while(~scanf("%d%llu",&n,&len)) 211 { 212 AC.init(); 213 memset(maps.m,0,sizeof(maps)); 214 char W[10]; 215 for(int i=1; i<=n; i++) 216 { 217 scanf(" %s",W); 218 AC.Insert(W,i); 219 } 220 AC.get_fail(); 221 AC.make_edge(); 222 AC.dfs(0,0); 223 AC.graph(); 224 for(int i=1;i<=2;i++) 225 { 226 for(int j=1;j<=2;j++) 227 { 228 printf(" %d ",maps.m[i][j]); 229 } 230 puts(""); 231 } 232 for(int i=1;i<=L+1;i++)maps.m[i][L+1]=1; 233 MAP ans = qpow(maps,len); 234 ull total = (qpow(26,len+1)-26)*qpow(25,(1ull<<63)-1); 235 for(int i=1;i<=L+1;i++)total -= ans.m[1][i]; 236 printf("%llu\n",total+1); 237 } 238 } View Code
一天,Lele在某本單詞書上看到了一個根據詞根來背單詞的方法。比如"ab",放在單詞前一般表示"相反,變壞,離去"等。
于是Lele想,如果背了N個詞根,那這些詞根到底會不會在單詞里出現呢。更確切的描述是:長度不超過L,只由小寫字母組成的,至少包含一個詞根的單詞,一共可能有多少個呢?這里就不考慮單詞是否有實際意義。
比如一共有2個詞根 aa 和 ab ,則可能存在104個長度不超過3的單詞,分別為
(2個) aa,ab,
(26個)aaa,aab,aac...aaz,
(26個)aba,abb,abc...abz,
(25個)baa,caa,daa...zaa,
(25個)bab,cab,dab...zab。
這個只是很小的情況。而對于其他復雜點的情況,Lele實在是數不出來了,現在就請你幫幫他。
Input本題目包含多組數據,請處理到文件結束。
每組數據占兩行。
第一行有兩個正整數N和L。(0<N<6,0<L<2^31)
第二行有N個詞根,每個詞根僅由小寫字母組成,長度不超過5。兩個詞根中間用一個空格分隔開。
Output對于每組數據,請在一行里輸出一共可能的單詞數目。
由于結果可能非常巨大,你只需要輸出單詞總數模2^64的值。
Sample Input
Sample Output
104 52思路:題目要求不超過L的所有符合單詞,我們先考慮等于L
對于所有滿足的情況 = 所有情況-不滿足情況
不滿足情況即是不包含詞根的單詞,對于計算滿足長度所有情況,可以聯想到矩陣快速冪,這樣我們就只需要建一個矩陣圖
對于建圖,我們知道,所有以詞根作為前綴子串的都是不符合條件的。
從根開始記錄每種前綴可以添加的字母路徑數(字母不是詞根的結尾)。
于是我們可以想到AC自動機,對于自動機補全的trie圖,每個單詞的fail都指向其最長子后綴
也就是從根以fail指針向下建圖的話,當遍歷到一個詞根末尾時,其下面的有詞根都時不合法的,這可以用dfs維護。
我們知道當矩陣中存有邊權(代表路徑數)時(相當于到每個點走一條路徑),我們將自身乘自身,得到的二次冪矩陣,每個位置的權值就相當于從i走到j走兩條路徑的路徑數目。(離散數學)
這樣推廣到這,就相當于算出矩陣的L次冪,其第一行的和就是從0長度到L長度經過各個路徑以各種字母結尾的路徑和,這是長度等于L的情況。
那么對于長度<=L的所有情況,我們知道?Σ(A1+A2+A3+...+AL),然后其第一行的和即是答案。
一開始寫的是遞歸,對于L=6為偶數,A1+A2+A3+A3(A1+A2+A3)
對于L=5為奇數,A1+A2+A2(A1+A2)+A5
但也許是姿勢不對,一直tle。
然后看了Discus,我們可以添加一維,(假設原本矩陣為L,此L!=題面的L),使得L+1列全是1,這樣對于第L個矩陣maps【1】【L+1】就儲存Σ(A1+A2+A3+...+A[L-1])
對于取模,因為是對1<<64取模,太大了直接取不了(睿智的嘗試),可以直接把相關數組開成unsign long long 可以直接對溢出舍去相當于取模
還有利用等比數列公式時,由于涉及除法,利用歐拉定理求出逆元即可。
附上丑代碼 1 #include<bits/stdc++.h> 2 using namespace std; 3 4 typedef unsigned long long ull; 5 struct Node 6 { 7 int y,next; 8 } node[41]; 9 int cnt,head[40],L; 10 int n; 11 ull len; 12 int tohash[40],topos; 13 void add(int x,int y) 14 { 15 node[++cnt].y=y; 16 node[cnt].next=head[x]; 17 head[x]=cnt; 18 } 19 20 struct MAP 21 { 22 ull m[40][40]; 23 friend MAP operator*(const MAP a,const MAP b) 24 { 25 MAP tmp; 26 for(int i=1; i<=L+1; i++) 27 { 28 for(int j=1; j<=L+1; j++) 29 { 30 tmp.m[i][j] = 0; 31 32 for(int k=1; k<=L+1; k++) 33 { 34 tmp.m[i][j] = tmp.m[i][j]+ a.m[i][k]*b.m[k][j]; 35 } 36 37 } 38 } 39 return tmp; 40 } 41 // friend MAP operator+(const MAP a,const MAP b) 42 // { 43 // MAP tmp; 44 // for(int i=1;i<=L;i++) 45 // { 46 // for(int j=1;j<=L+;j++) 47 // { 48 // tmp.m[i][j] = a.m[i][j] + b.m[i][j]; 49 // } 50 // } 51 // return tmp; 52 // } 53 void init() 54 { 55 for(int i=1; i<=L+1; i++) 56 { 57 for(int j=1; j<=L+1; j++) 58 { 59 m[i][j] = 0; 60 } 61 m[i][i] = 1; 62 } 63 } 64 } maps; 65 struct ACTO 66 { 67 int trie[40][26],tot; 68 int dp[40]; 69 int fail[40]; 70 int End[40]; 71 void init() 72 { 73 tot = cnt = L = topos = 0; 74 memset(trie,0,sizeof(trie)); 75 memset(maps.m,0,sizeof(maps.m)); 76 memset(head,0,sizeof(head)); 77 memset(dp,0,sizeof(dp)); 78 memset(fail,0,sizeof(fail)); 79 memset(End,0,sizeof(End)); 80 memset(tohash,0,sizeof(tohash)); 81 } 82 void Insert(char *str,int num) 83 { 84 int len = strlen(str),p=0; 85 for(int i=0; i<len; i++) 86 { 87 int ch = str[i]-'a'; 88 if(!trie[p][ch])trie[p][ch]=++tot; 89 p = trie[p][ch]; 90 } 91 End[p]=num; 92 } 93 void get_fail() 94 { 95 queue<int>que; 96 while(!que.empty())que.pop(); 97 for(int i=0; i<26; i++)if(trie[0][i])que.push(trie[0][i]); 98 while(!que.empty()) 99 { 100 int p = que.front(); 101 que.pop(); 102 for(int i=0; i<26; i++) 103 { 104 if(trie[p][i]) 105 { 106 fail[trie[p][i]] = trie[fail[p]][i]; 107 que.push(trie[p][i]); 108 } 109 else trie[p][i] = trie[fail[p]][i]; 110 } 111 } 112 } 113 void make_edge() 114 { 115 for(int i=1; i<=tot; i++) 116 { 117 add(fail[i],i); 118 } 119 } 120 void dfs(int x,int f) 121 { 122 if(End[x])f=1; 123 dp[x] = f; 124 for(int i=head[x]; i; i=node[i].next) 125 { 126 dfs(node[i].y,f); 127 } 128 129 } 130 void graph() 131 { 132 for(int i=0; i<=tot; i++) 133 { 134 if(!dp[i]) 135 { 136 L++; 137 if(!tohash[i])tohash[i] = ++topos; 138 for(int j=0; j<26; j++) 139 { 140 if(!dp[trie[i][j]]) 141 { 142 if(!tohash[trie[i][j]])tohash[trie[i][j]]=++topos; 143 maps.m[tohash[i]][tohash[trie[i][j]]]++; 144 } 145 } 146 } 147 } 148 } 149 }; 150 151 MAP qpow(MAP a,ull b) 152 { 153 MAP ans; 154 ans.init(); 155 MAP base = a; 156 while(b) 157 { 158 if(b&1)ans = ans*base; 159 base = base * base; 160 b >>= 1; 161 } 162 return ans; 163 } 164 165 ACTO AC; 166 167 //MAP cal(int l,int r) 168 //{ 169 // 170 // if(l==r)return maps; 171 // int mid = r/2; 172 // if(r & 1) 173 // { 174 // return cal(l,mid)+qpow(maps,mid)*cal(l,mid)+qpow(maps,r); 175 // } 176 // else 177 // { 178 // return cal(l,mid)+cal(l,mid)*qpow(maps,mid); 179 // } 180 //} 181 182 183 184 ull qmulti(ull m,ull n) 185 { 186 ull ans = 0; 187 while(n) 188 { 189 if(n&1)ans += m; 190 m += m; 191 n >>= 1; 192 } 193 return ans; 194 } 195 ull qpow(ull a,ull b) 196 { 197 ull ans = 1; 198 ull base = a; 199 while(b) 200 { 201 if(b&1)ans = qmulti(ans,base); 202 base = qmulti(base,base); 203 b >>= 1; 204 } 205 return ans; 206 } 207 208 int main() 209 { 210 while(~scanf("%d%llu",&n,&len)) 211 { 212 AC.init(); 213 memset(maps.m,0,sizeof(maps)); 214 char W[10]; 215 for(int i=1; i<=n; i++) 216 { 217 scanf(" %s",W); 218 AC.Insert(W,i); 219 } 220 AC.get_fail(); 221 AC.make_edge(); 222 AC.dfs(0,0); 223 AC.graph(); 224 for(int i=1;i<=2;i++) 225 { 226 for(int j=1;j<=2;j++) 227 { 228 printf(" %d ",maps.m[i][j]); 229 } 230 puts(""); 231 } 232 for(int i=1;i<=L+1;i++)maps.m[i][L+1]=1; 233 MAP ans = qpow(maps,len); 234 ull total = (qpow(26,len+1)-26)*qpow(25,(1ull<<63)-1); 235 for(int i=1;i<=L+1;i++)total -= ans.m[1][i]; 236 printf("%llu\n",total+1); 237 } 238 } View Code
?
轉載于:https://www.cnblogs.com/iwannabe/p/10772324.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的HDU 2243考研路茫茫——单词情结 (AC自动机+矩阵快速幂)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 个人所得税怎么交 一般由公司代扣代缴
- 下一篇: oracle查看被锁的表和解锁