3.3 字符串(1)
Trie 前缀树 字典树
例题11 LA 3942 http://acm.hust.edu.cn/vjudge/problem/22109
字典树第一个例题我用set水过,先记录这个版本吧,题意是,给一个串,还有一些短串的集合,问有多少种不同的拆分方法使得拆分完后,每个子串都在集合中。
dp[len] 表示前len个有多少拆分法 dp[0]=1, 前0个字符有一种拆分法,空嘛。 dp[len] 就是答案, 转移就是, 如果接下来一段在集合中,那么就可以转移到长度加这段的长度。
起点有la=3e5,,对每一个起点,加的串可能有lbi=100,都要枚举,接下来就是判断这个串是否存在,用hash值判,o1,set查询logn ,n=4000, 总体复杂度 la*lb*logn 飘过2700ms。
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=3e5+10; 11 const LL MOD=20071027; 12 class String_Hash {///字符串哈希 init O(n) query O(1) 13 typedef unsigned long long typec;///hash 值类型 14 static const int MV=3e5+10;///串长度 15 static const int key=137;///hash 种子值选素数好 16 typec H[MV],xp[MV]; 17 public: 18 void init(char s[],int ls) { ///传入串数组和串长 19 H[ls]=0; 20 for(int i=ls-1; i>=0; i--) { 21 H[i]=H[i+1]*key+s[i]; 22 } 23 xp[0]=1; 24 for(int i=1; i<=ls; i++) { 25 xp[i]=xp[i-1]*key; 26 } 27 } 28 typec get(int pos,int len) { ///传入初始位置 pos,串长 len,返回串 hash 值 29 return H[pos]-H[pos+len]*xp[len]; 30 } 31 }Hash; 32 int n; 33 char a[M]; 34 char b[4010][110]; 35 LL dp[M]; 36 set<unsigned long long> s; 37 int max_len; 38 void init_set() { 39 s.clear(); 40 max_len=0; 41 for(int i=0; i<n; i++) { 42 int lb=strlen(b[i]); 43 Hash.init(b[i],lb); 44 s.insert(Hash.get(0,lb)); 45 max_len=max(max_len,lb); 46 } 47 } 48 LL solve() { 49 init_set(); 50 int len=strlen(a); 51 for(int i=0; i<=len; i++) { 52 dp[i]=0; 53 } 54 dp[0]=1; 55 Hash.init(a,strlen(a)); 56 for(int i=0; i<len; i++) { 57 if(dp[i]==0) continue; 58 for(int j=0; j<max_len; j++) { 59 if(!s.count(Hash.get(i,j+1))) continue; 60 dp[i+j+1]+=dp[i]; 61 dp[i+j+1]%=MOD; 62 } 63 } 64 return dp[len]; 65 } 66 int main() { 67 #ifdef txtout 68 freopen("in.txt","r",stdin); 69 freopen("out.txt","w",stdout); 70 #endif // txtout 71 int cas=1; 72 while(~scanf("%s%d",a,&n)) { 73 for(int i=0; i<n; i++) { 74 scanf("%s",b[i]); 75 } 76 printf("Case %d: %lld\n",cas++,solve()); 77 } 78 return 0; 79 }
字典树特别快72ms. 与上一种方法的差别就在于,用字典树存小串集合,然后还是枚举起点, 更新的时候就在字典树里更新, 遇到一个串就更新一个dp, 最坏情况复杂度 la*lb.
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=3e5+10; 11 const LL MOD=20071027; 12 class Trie_tree { //字典树 前缀树 13 struct node { 14 int next[26],ptr,num;// ptr在字典中的位置,num此结点出现几次, 15 void init() { 16 ptr=-1; 17 num=0; 18 mt(next,0); 19 } 20 } s[M]; 21 int ls,p; 22 int f(char c) { 23 return c-'a'; 24 } 25 public: 26 void init() { 27 s[1].init(); 28 p=0; 29 ls=2; 30 } 31 void Insert(char ch[]) { 32 int head=1; 33 for(int k=0; ch[k]; k++) { 34 int id=f(ch[k]); 35 if(!s[head].next[id]) { 36 s[head].next[id]=ls; 37 s[ls++].init(); 38 } 39 head=s[head].next[id]; 40 s[head].num++; 41 } 42 s[head].ptr=p++; 43 } 44 int query(char ch[],int op) { 45 int head=1; 46 for(int k=0; ch[k]&&head; k++) { 47 head=s[head].next[f(ch[k])]; 48 } 49 if(op) { //op==1 查ptr 50 if(head) return s[head].ptr; 51 return -1; 52 } else { //op==0 查num 53 if(head) return s[head].num; 54 return 0; 55 } 56 } 57 void solve(int i,char ch[],LL dp[]){ 58 int head=1; 59 for(int k=i;ch[k]&&head;k++){ 60 int id=f(ch[k]); 61 if(!s[head].next[id]) return ; 62 head=s[head].next[id]; 63 if(s[head].ptr!=-1){ 64 dp[k+1]+=dp[i]; 65 dp[k+1]%=MOD; 66 } 67 } 68 } 69 } trie; 70 int n; 71 char a[M]; 72 char b[4010][110]; 73 LL dp[M]; 74 void init_trie() { 75 trie.init(); 76 for(int i=0; i<n; i++) { 77 trie.Insert(b[i]); 78 } 79 } 80 LL solve() { 81 init_trie(); 82 int len=strlen(a); 83 for(int i=0; i<=len; i++) { 84 dp[i]=0; 85 } 86 dp[0]=1; 87 for(int i=0; i<len; i++) { 88 if(dp[i]==0) continue; 89 trie.solve(i,a,dp); 90 } 91 return dp[len]; 92 } 93 int main() { 94 #ifdef txtout 95 freopen("in.txt","r",stdin); 96 freopen("out.txt","w",stdout); 97 #endif // txtout 98 int cas=1; 99 while(~scanf("%s%d",a,&n)) { 100 for(int i=0; i<n; i++) { 101 scanf("%s",b[i]); 102 } 103 printf("Case %d: %lld\n",cas++,solve()); 104 } 105 return 0; 106 }
例题12 uva 11732 http://acm.hust.edu.cn/vjudge/problem/28438
题意, 给n个字符串,用题目中的strcmp两两比较,需要执行多少次==判断.
暴力n2枚举, 最坏len计算次数.超时.
字典树做法,先把所有串插入字典树, 然后枚举一个串去字典树中查询, 对于串的每一位,都需要和所有串执行一次==,通过字典树节点记录的次数可以o1累加, 然后和该位置相等的串会多执行一次=='\0',也可以o1得到.
因为\0也会比较一次,而字典树中我没存,所以我在每个串后面加个#表示\0,这样比较时不需要特判他了. 然后每个串会多算一个和自己的次数, 扣去2倍的长度即可,因为和自己总是能比较到最后一位. 每一对会算两次,最后答案除2即可.
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=4e3+10; 11 class Trie_tree { //字典树 前缀树 12 struct node { 13 int next[63],ptr,num;// ptr在字典中的位置,num此结点出现几次, 14 void init() { 15 ptr=-1; 16 num=0; 17 mt(next,0); 18 } 19 } s[M*1000]; 20 int ls,p; 21 int f(char c) { 22 if(c>='a'&&c<='z') return c-'a'; 23 if(c>='A'&&c<='Z') return c-'A'+26; 24 if(c>='0'&&c<='9') return c-'0'+52; 25 return 62; 26 } 27 public: 28 void init() { 29 s[1].init(); 30 p=0; 31 ls=2; 32 } 33 void Insert(char ch[]) { 34 int head=1; 35 for(int k=0; ch[k]; k++) { 36 int id=f(ch[k]); 37 if(!s[head].next[id]) { 38 s[head].next[id]=ls; 39 s[ls++].init(); 40 } 41 head=s[head].next[id]; 42 s[head].num++; 43 } 44 s[head].ptr=p++; 45 } 46 int query(char ch[],int op) { 47 int head=1; 48 for(int k=0; ch[k]&&head; k++) { 49 head=s[head].next[f(ch[k])]; 50 } 51 if(op) { //op==1 查ptr 52 if(head) return s[head].ptr; 53 return -1; 54 } else { //op==0 查num 55 if(head) return s[head].num; 56 return 0; 57 } 58 } 59 LL solve(char ch[]){ 60 LL sum=0; 61 int head=1; 62 LL presum=-1; 63 for(int k=0; ch[k]&&head; k++) { 64 if(presum==-1){ 65 presum=0; 66 for(int i=0;i<63;i++){ 67 int next=s[head].next[i]; 68 if(!next) continue; 69 presum+=s[next].num; 70 } 71 } 72 sum+=presum; 73 head=s[head].next[f(ch[k])]; 74 sum+=s[head].num; 75 presum=s[head].num; 76 } 77 return sum; 78 } 79 } trie; 80 int n; 81 char a[M][M]; 82 LL solve(){ 83 trie.init(); 84 for(int i=0;i<n;i++){ 85 strcat(a[i],"#"); 86 trie.Insert(a[i]); 87 } 88 LL sum=0; 89 for(int i=0;i<n;i++){ 90 sum+=trie.solve(a[i]); 91 sum-=strlen(a[i])*2; 92 } 93 return sum/2; 94 } 95 int main(){ 96 #ifdef txtout 97 freopen("in.txt","r",stdin); 98 freopen("out.txt","w",stdout); 99 #endif // txtout 100 int cas=1; 101 while(~scanf("%d",&n),n){ 102 for(int i=0;i<n;i++){ 103 scanf("%s",a[i]); 104 } 105 printf("Case %d: %lld\n",cas++,solve()); 106 } 107 return 0; 108 }
例题13 la 3026 http://acm.hust.edu.cn/vjudge/problem/29342
kmp例题, 又拿hash水过了, 发现hash虽然理论上有概率冲突,有概率出错, 但是实际使用起来, 很好.
这题对每一个前缀,想知道该前缀最多能分成几块, 使得每一块都相等, 如果块数大于1,就输出.
hash解法, 枚举一个前缀, 然后枚举该前缀往后能有多少段相等, 每找到一个相等, 就能更新一次. 复杂度 n *( 1/1 + 1/2 + 1/n) 调和级数 并不大。 好像ln n吧?
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=1e6+10; 11 class String_Hash {///字符串哈希 init O(n) query O(1) 12 typedef unsigned long long typec;///hash 值类型 13 static const int MV=1e6+10;///串长度 14 static const int key=137;///hash 种子值选素数好 15 typec H[MV],xp[MV]; 16 public: 17 void init(char s[],int ls) { ///传入串数组和串长 18 H[ls]=0; 19 for(int i=ls-1; i>=0; i--) { 20 H[i]=H[i+1]*key+s[i]; 21 } 22 xp[0]=1; 23 for(int i=1; i<=ls; i++) { 24 xp[i]=xp[i-1]*key; 25 } 26 } typec get(int pos,int len) { ///传入初始位置 pos,串长 len,返回串 hash 值 27 return H[pos]-H[pos+len]*xp[len]; 28 } 29 } gx; 30 int n; 31 char a[M]; 32 int b[M]; 33 typedef pair<int,int> pii; 34 vector<pii> answer; 35 void init() { 36 int la=strlen(a); 37 for(int i=0; i<la; i++) { 38 b[i]=1; 39 } 40 gx.init(a,la); 41 } 42 void solve() { 43 init(); 44 int la=strlen(a); 45 for(int i=0;i<la;i++){ 46 int len=i+1; 47 for(int j=i+1;j+len-1<la;j+=len){ 48 if(gx.get(0,len)!=gx.get(j,len)) break; 49 b[j+len-1]=max(b[j+len-1],(j+len)/len); 50 } 51 } 52 answer.clear(); 53 for(int i=0;i<la;i++){ 54 if(b[i]>1){ 55 answer.push_back(make_pair(i+1,b[i])); 56 } 57 } 58 } 59 int main() { 60 #ifdef txtout 61 freopen("in.txt","r",stdin); 62 freopen("out.txt","w",stdout); 63 #endif // txtout 64 int cas=1; 65 while(~scanf("%d",&n),n) { 66 scanf("%s",a); 67 solve(); 68 printf("Test case #%d\n",cas++); 69 for(int i=0; i<answer.size(); i++) { 70 printf("%d %d\n",answer[i].first,answer[i].second); 71 } 72 puts(""); 73 } 74 return 0; 75 }
白书kmp不是很容易理解 , 先记录一下吧.
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=1e6+10; 11 int n; 12 char a[M]; 13 int f[M]; 14 typedef pair<int,int> pii; 15 vector<pii> answer; 16 void getFail(char* P,int* f){ 17 int m=strlen(P); 18 f[0]=0; 19 f[1]=0; 20 for(int i=1;i<m;i++){ 21 int j=f[i]; 22 while(j&&P[i]!=P[j]) j=f[j]; 23 f[i+1]=P[i]==P[j]?j+1:0; 24 } 25 } 26 void solve() { 27 getFail(a,f); 28 answer.clear(); 29 int la=strlen(a); 30 for(int i=0;i<=la;i++){ 31 if(f[i]>0&&i%(i-f[i])==0){ 32 answer.push_back(make_pair(i,i/(i-f[i]))); 33 } 34 } 35 } 36 int main() { 37 #ifdef txtout 38 freopen("in.txt","r",stdin); 39 freopen("out.txt","w",stdout); 40 #endif // txtout 41 int cas=1; 42 while(~scanf("%d",&n),n) { 43 scanf("%s",a); 44 solve(); 45 printf("Test case #%d\n",cas++); 46 for(int i=0; i<answer.size(); i++) { 47 printf("%d %d\n",answer[i].first,answer[i].second); 48 } 49 puts(""); 50 } 51 return 0; 52 }
例题14 la 4670 http://acm.hust.edu.cn/vjudge/problem/19224
ac自动机例题 不会, 但是kmp可以水过. 求出现次数最多的子串. 暴力一个个kmp看出现次数.
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=1e6+10; 11 class KMP { ///模式匹配(kmp)O(ls+lp) 12 typedef char typec;///文本元素的类型 13 static const int MV=1e6+10;///字符串的长度 14 int next[MV]; 15 public:///匹配串长度 ls,str 存待匹配文本,模式串长度 lp,pat 存模式串 16 int kmp(int ls,typec str[],int lp,typec pat[],int res[]) { ///返回匹配次数,res 中存储每个匹配的初始位置 17 int i=0,j=-1,cnt=0; 18 next[0]=-1; 19 while(i<lp) { 20 if(j==-1||pat[i]==pat[j]) { 21 next[++i]=++j; 22 continue; 23 } 24 j=next[j]; 25 } 26 i=j=0; 27 while(i<ls) { 28 while(j!=lp&&str[i]==pat[j]) { 29 i++; 30 j++; 31 } 32 if(!j) { 33 i++; 34 continue; 35 } 36 if(j==lp) { 37 res[cnt++]=i-j; 38 } 39 j=next[j]; 40 } 41 return cnt; 42 } 43 } gx; 44 int n; 45 struct Q{ 46 char a[128]; 47 int time; 48 }q[210]; 49 char b[M]; 50 int res[M]; 51 int answer; 52 void solve(){ 53 answer=0; 54 int lb=strlen(b); 55 for(int i=0;i<n;i++){ 56 q[i].time=gx.kmp(lb,b,strlen(q[i].a),q[i].a,res); 57 answer=max(answer,q[i].time); 58 } 59 } 60 int main() { 61 #ifdef txtout 62 freopen("in.txt","r",stdin); 63 freopen("out.txt","w",stdout); 64 #endif // txtout 65 while(~scanf("%d",&n),n) { 66 for(int i=0; i<n; i++) { 67 scanf("%s",q[i].a); 68 } 69 scanf("%s",b); 70 solve(); 71 printf("%d\n",answer); 72 for(int i=0;i<n;i++){ 73 if(q[i].time==answer){ 74 puts(q[i].a); 75 } 76 } 77 } 78 return 0; 79 }
ac自动机代码 need to write.
例题15 uva 11468
ac自动机 need to write.
例题16 uva 11019
暴力kmp加bitset 水过. 求一个字符矩阵在一个大字符矩阵出现几次. 枚举每一行, bitset每一位 1 表示该位为起点匹配上了, x行与运算, 1的个数就是矩阵匹配次数, 中间也可以break 剪枝.
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=1e3+10; 11 class KMP { ///模式匹配(kmp)O(ls+lp) 12 typedef char typec;///文本元素的类型 13 static const int MV=1e3+10;///字符串的长度 14 int next[MV]; 15 public:///匹配串长度 ls,str 存待匹配文本,模式串长度 lp,pat 存模式串 16 int kmp(int ls,typec str[],int lp,typec pat[],int res[]) { ///返回匹配次数,res 中存储每个匹配的初始位置 17 int i=0,j=-1,cnt=0; 18 next[0]=-1; 19 while(i<lp) { 20 if(j==-1||pat[i]==pat[j]) { 21 next[++i]=++j; 22 continue; 23 } 24 j=next[j]; 25 } 26 i=j=0; 27 while(i<ls) { 28 while(j!=lp&&str[i]==pat[j]) { 29 i++; 30 j++; 31 } 32 if(!j) { 33 i++; 34 continue; 35 } 36 if(j==lp) { 37 res[cnt++]=i-j; 38 } 39 j=next[j]; 40 } 41 return cnt; 42 } 43 } gx; 44 int n,m,x,y; 45 char a[M][M]; 46 char b[M][M]; 47 int la[M]; 48 int lb[M]; 49 bitset<1000> b1,b2; 50 int res[M]; 51 int solve(){ 52 for(int i=0;i<n;i++){ 53 la[i]=strlen(a[i]); 54 } 55 for(int i=0;i<x;i++){ 56 lb[i]=strlen(b[i]); 57 } 58 int sum=0; 59 for(int i=0;i+x-1<n;i++){ 60 b1.reset(); 61 for(int j=0;j<m;j++){ 62 b1.set(j); 63 } 64 for(int j=0;j<x;j++){ 65 int time=gx.kmp(la[i+j],a[i+j],lb[j],b[j],res); 66 if(time==0){ 67 b1.reset(); 68 break; 69 } 70 b2.reset(); 71 for(int k=0;k<time;k++){ 72 b2.set(res[k]); 73 } 74 b1&=b2; 75 if(b1.count()==0) break; 76 } 77 sum+=b1.count(); 78 } 79 return sum; 80 } 81 int main() { 82 #ifdef txtout 83 freopen("in.txt","r",stdin); 84 freopen("out.txt","w",stdout); 85 #endif // txtout 86 int t; 87 while(~scanf("%d",&t)) { 88 while(t--){ 89 scanf("%d%d",&n,&m); 90 for(int i=0;i<n;i++){ 91 scanf("%s",a[i]); 92 } 93 scanf("%d%d",&x,&y); 94 for(int i=0;i<x;i++){ 95 scanf("%s",b[i]); 96 } 97 printf("%d\n",solve()); 98 } 99 } 100 return 0; 101 }
不用bitset会慢一些, 常数
1 //#define txtout 2 //#define debug 3 #include<bits/stdc++.h> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 typedef long long LL; 7 const double pi=acos(-1.0); 8 const double eps=1e-8; 9 const int inf=0x3f3f3f3f; 10 const int M=1e3+10; 11 class KMP { ///模式匹配(kmp)O(ls+lp) 12 typedef char typec;///文本元素的类型 13 static const int MV=1e3+10;///字符串的长度 14 int next[MV]; 15 public:///匹配串长度 ls,str 存待匹配文本,模式串长度 lp,pat 存模式串 16 int kmp(int ls,typec str[],int lp,typec pat[],int res[]) { ///返回匹配次数,res 中存储每个匹配的初始位置 17 int i=0,j=-1,cnt=0; 18 next[0]=-1; 19 while(i<lp) { 20 if(j==-1||pat[i]==pat[j]) { 21 next[++i]=++j; 22 continue; 23 } 24 j=next[j]; 25 } 26 i=j=0; 27 while(i<ls) { 28 while(j!=lp&&str[i]==pat[j]) { 29 i++; 30 j++; 31 } 32 if(!j) { 33 i++; 34 continue; 35 } 36 if(j==lp) { 37 res[cnt++]=i-j; 38 } 39 j=next[j]; 40 } 41 return cnt; 42 } 43 } gx; 44 int n,m,x,y; 45 char a[M][M]; 46 char b[M][M]; 47 int la[M]; 48 int lb[M]; 49 bool b1[M]; 50 bool b2[M]; 51 int res[M]; 52 int solve(){ 53 for(int i=0;i<n;i++){ 54 la[i]=strlen(a[i]); 55 } 56 for(int i=0;i<x;i++){ 57 lb[i]=strlen(b[i]); 58 } 59 int sum=0; 60 for(int i=0;i+x-1<n;i++){ 61 int one=m; 62 for(int j=0;j<m;j++){ 63 b1[j]=true; 64 } 65 for(int j=0;j<x;j++){ 66 int time=gx.kmp(la[i+j],a[i+j],lb[j],b[j],res); 67 if(time==0){ 68 one=0; 69 break; 70 } 71 for(int k=0;k<m;k++){ 72 b2[k]=false; 73 } 74 for(int k=0;k<time;k++){ 75 b2[res[k]]=true; 76 } 77 for(int k=0;k<m;k++){ 78 if(b1[k]&&b2[k]) continue; 79 if(b1[k]&&!b2[k]){ 80 b1[k]=false; 81 one--; 82 if(one==0) break; 83 } 84 } 85 if(one==0) break; 86 } 87 sum+=one; 88 } 89 return sum; 90 } 91 int main() { 92 #ifdef txtout 93 freopen("in.txt","r",stdin); 94 freopen("out.txt","w",stdout); 95 #endif // txtout 96 int t; 97 while(~scanf("%d",&t)) { 98 while(t--){ 99 scanf("%d%d",&n,&m); 100 for(int i=0;i<n;i++){ 101 scanf("%s",a[i]); 102 } 103 scanf("%d%d",&x,&y); 104 for(int i=0;i<x;i++){ 105 scanf("%s",b[i]); 106 } 107 printf("%d\n",solve()); 108 } 109 } 110 return 0; 111 }
ac自动机 need to write.
end