3.3 字符串(1)
分类:
IT文章
•
2023-11-05 22:24:29
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
",cas++,solve());
77 }
78 return 0;
79 }
View Code
字典树特别快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
",cas++,solve());
104 }
105 return 0;
106 }
View Code
例题12 uva 11732 http://acm.hust.edu.cn/vjudge/problem/28438
题意, 给n个字符串,用题目中的strcmp两两比较,需要执行多少次==判断.
暴力n2枚举, 最坏len计算次数.超时.
字典树做法,先把所有串插入字典树, 然后枚举一个串去字典树中查询, 对于串的每一位,都需要和所有串执行一次==,通过字典树节点记录的次数可以o1累加, 然后和该位置相等的串会多执行一次==' ',也可以o1得到.
因为 也会比较一次,而字典树中我没存,所以我在每个串后面加个#表示 ,这样比较时不需要特判他了. 然后每个串会多算一个和自己的次数, 扣去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
",cas++,solve());
106 }
107 return 0;
108 }
View Code
例题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
",cas++);
69 for(int i=0; i<answer.size(); i++) {
70 printf("%d %d
",answer[i].first,answer[i].second);
71 }
72 puts("");
73 }
74 return 0;
75 }
View Code
白书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
",cas++);
46 for(int i=0; i<answer.size(); i++) {
47 printf("%d %d
",answer[i].first,answer[i].second);
48 }
49 puts("");
50 }
51 return 0;
52 }
View Code
例题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
",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 }
View Code
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
",solve());
98 }
99 }
100 return 0;
101 }
View Code
不用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
",solve());
108 }
109 }
110 return 0;
111 }
View Code
ac自动机 need to write.
end