bzoj 1212 [HNOI2004] L语言(不用AC自动机)
网上的题解大多树都要建一棵trie树,并在上面跑AC自动机,然而这里有一种同样需要trie树,但时间复杂度较低的方法。
首先,我们可以轻松列出状态转移方程 F[x]=∑| F[x-len(i)]&(is(i->x,s[i]);
这样的复杂度是O(m*lens*∑len[i]*n),可能会超时,再加上hash之类的就可以过了,但这显然不优美。
====================分割线====================
对于每个F(i),我们都是从之前的额某个F(j)转移过来的,它是true当且仅当(j+1->i)是一个单词,且f[j]是true,那么我们将每个单词反过来建一棵trie树,例如有单词abc,我们将cba插入trie树,从i开始i先匹配到每个单词的最后一位,然后再匹配到最后一位相同的倒数第二位,如此下去,当我们匹配到一个单词的开头时,并且此时的F[i-depth]为true的话,F(i)就为true了,因为每个字符在trie树上的路径唯一,且trie树的深度不超过单词的最长长度(10),所以它的复杂度还是非常可看的,复杂度为O(m*lens*dep(trie))=O(m*lens*max(strlen(word))),20*1M*10,轻松过。
这题不难,但是如果反过来想,可以避免很多高端算法,从后往前的思想确实不错。贴一个代码,有些冗长。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 using namespace std; 6 int ch[10005][20]; 7 int cnt; 8 int n,m; 9 char s[2000005]; 10 int col[2000005]; 11 bool dp[1000005]; 12 void insert() 13 { 14 int now=0; 15 for(int i=strlen(s)-1;i>=0;i--) 16 { 17 int c=s[i]-'0'; 18 if(!ch[now][c]) 19 { 20 cnt++; 21 ch[now][c]=cnt; 22 } 23 now=ch[now][c]; 24 } 25 col[now]=true; 26 } 27 bool cal(int x) 28 { 29 int deep=0; 30 int now=0; 31 for(int i=x;i>=1;i--) 32 { 33 int c=s[i]-'0'; 34 if(ch[now][c]==0)return false; 35 now=ch[now][c]; 36 deep++; 37 if(dp[x-deep] && col[now])return true; 38 } 39 return true; 40 } 41 void solve() 42 { 43 int ll=strlen(s+1); 44 for(int i=1;i<=ll;i++) 45 { 46 dp[i]=cal(i); 47 } 48 for(int i=ll;i>=0;i--) 49 { 50 if(dp[i]) 51 { 52 printf("%d\n",i); 53 return; 54 } 55 } 56 return ; 57 } 58 int main() 59 { 60 scanf("%d%d",&n,&m); 61 for(int i=1;i<=n;i++) 62 { 63 scanf("%s",s); 64 insert(); 65 } 66 for(int i=1;i<=m;i++) 67 { 68 memset(dp,0,sizeof(dp)); 69 dp[0]=true; 70 scanf("%s",s+1); 71 solve(); 72 } 73 return 0; 74 }