基础字符串学习记录

记录一些简单的基础字符串知识 题目会有编号

等待重新排版233(并没有注意到格式和word不一样...在电脑上写完直接粘过来了....)

我好懵B啊...貌似不少东西都写错了..然后还能A??..明明随手造一组就挂掉的...数据怎么这么水..我还是重新写吧(之前代码全部删掉了...这坑真的要填不完了...)....

1.KMP

这个算法应该很基础了吧,最基本的应用:O(n+m)找出文本串在模板串中出现的位置。不过有很多关于nxt数组的神奇应用。

①    重复的密文

大概题意:有一个小写字符串鬼畜了,重复出现拼成了一个长串,现在给你长串,求原始串的最短长度是多少。长串长度是10^6

样例输入:

8

cabcabca

样例输出:

3

因为是重复出现...所以直接求出nxt数组,答案就是  len-1-nxt[len-1]  (下标从0开始,我的定义可能跟其他人不一样...) 

下面是代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 char s[1000010];
 7 int nxt[1000010];
 8 using namespace std;
 9 int main()
10 {
11     int i,j,len;
12     scanf("%d",&len);
13     scanf("%s",s);
14     nxt[0]=-1;
15     i=-1,j=0;
16     while(j<len){
17         if(i==-1||s[i]==s[j]){
18             i++,j++;
19             nxt[j]=i;
20         }
21         else i=nxt[i];
22     }
23     printf("%d",len-1-nxt[len-1]);
24 }

②    阿里天池的新任务

大概题意:告诉你字符串的生成方法,先生成一个长串(作为模板串),之后给你一个短串(作为文本串),问短串在长串里出现了多少次。两个串长度均小于10^6

样例输入1:

13 2 5 4 9

AGG

样例输出1:

1

样例输入2:

103 51 0 40 60

ACTG

样例输出2:

5

 这道题是KMP最模板的东西了吧...同时贴出poj3461代码(因为这道题生成规则实在是太麻烦...)

本题代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 using namespace std;
 7 char s1[1000010],s2[1000010];
 8 int w[1000010],nxt[1000010];
 9 int main()
10 {
11     int n,i,j,a,b,l,r,len,ans=0;
12     scanf("%d%d%d%d%d",&n,&a,&b,&l,&r);
13     scanf("%s",s2);
14     w[0]=b;len=strlen(s2);
15     for(i=1;i<n;i++) w[i]=(w[i-1]+a)%n;
16     for(i=0;i<n;i++){
17         if(l<=w[i]&&w[i]<=r){
18             if(w[i]%2==0) s1[i]='A';
19             else s1[i]='T';
20         }
21         else{
22             if(w[i]%2==0) s1[i]='G';
23             else s1[i]='C';
24         }
25     }
26     nxt[0]=-1;
27     i=-1,j=0;
28     while(j<len){
29         if(i==-1||s2[i]==s2[j]){
30             i++,j++;
31             nxt[j]=i;
32         }
33         else i=nxt[i];
34     }
35     i=j=0;
36     while(i<n){
37         if(j==-1||s1[i]==s2[j]) i++,j++;
38         else j=nxt[j];
39         if(j==len){
40             ans++;
41             j=nxt[j];
42         }
43     }
44     printf("%d",ans);
45 }

 Poj3461代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 using namespace std;
 7 int len1,len2,ans,nxt[10010];
 8 char s1[1000010],s2[10010];
 9 void getnxt(){
10     int p1=-1,p2=0;
11     nxt[0]=-1;
12     while(p2<len2){
13         if(p1==-1||s2[p1]==s2[p2]){
14             p1++;p2++;
15             nxt[p2]=p1;
16         }
17         else p1=nxt[p1];
18     }
19 }
20 void kmp(){
21     int p2=0,p1=0;ans=0;
22     while(p1<len1){
23         if(p2==-1||s2[p2]==s1[p1]){p1++;p2++;}
24         else p2=nxt[p2];
25         if(p2==len2){
26             ans++;
27             p2=nxt[p2];
28         }
29     }
30 }
31 int main()
32 {
33     int t;
34     scanf("%d",&t);
35     while(t--){
36         scanf("%s%s",s2,s1);
37         len1=strlen(s1);
38         len2=strlen(s2);
39         getnxt();
40         kmp();
41         printf("%d\n",ans);
42     }
43 }

 2.扩展KMP

这个算法并没有耐心学下去了,之前貌似学过...然而早就忘光了...至于题目可以用其他方法骗过去23333

③    首尾相接

大概题意:给两个串S1 S2,找出一个字串使得这个字串是S1的前缀,并且是S2的后缀,输出最长满足条件的字串。S1 S2长度均为50000。

 样例输入:

riemann

marjorie

样例输出:

rie 3

表示并不会EXKMP...貌似可以后缀数组...?说一下后缀数组的做法:把两个串拼在一起,直接跑出sa和h,找到sa[i]==len(前一个串的长度),然后答案就是从第二个串开始,总共长度有h[i+1]。

下面代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 #define N 100010
 7 using namespace std;
 8 char s[N],s1[N];
 9 int sa[N],xx[N],yy[N],wv[N],num[N],rk[N],h[N],n,m;
10 bool cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
11 void hou(){
12     int i,j,p,k=0,*x=xx,*y=yy;
13     for(i=0;i<m;i++) num[i]=0;
14     for(i=0;i<n;i++) num[x[i]=s[i]]++;
15     for(i=1;i<m;i++) num[i]+=num[i-1];
16     for(i=n-1;i>=0;i--) sa[--num[x[i]]]=i;
17     for(j=1,p=1;p<n;j*=2,m=p){
18         p=0;
19         for(i=n-j;i<n;i++) y[p++]=i;
20         for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
21         for(i=0;i<n;i++) wv[i]=x[y[i]];
22         for(i=0;i<m;i++) num[i]=0;
23         for(i=0;i<n;i++) num[wv[i]]++;
24         for(i=1;i<m;i++) num[i]+=num[i-1];
25         for(i=n-1;i>=0;i--) sa[--num[wv[i]]]=y[i];
26         swap(x,y),p=1,x[sa[0]]=0;
27         for(i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
28     }
29     for(i=0;i<n;i++) rk[sa[i]]=i;
30     for(i=0;i<n;h[rk[i++]]=k){for(k?k--:0,j=sa[rk[i]-1];s[i+k]==s[j+k];k++);}
31     return;
32 }
33 int main()
34 {
35     int i,j,len,mx;m=30;
36     scanf("%s%s",s1,s);
37     n=strlen(s);
38     len=strlen(s1);
39     s[n]='z'+1;
40     for(i=0;i<len;i++) s[n+i+1]=s1[i];
41     len=n+1;
42     n=strlen(s);
43     for(i=0;i<n;i++) s[i]=s[i]-'a'+1;
44     n++;hou();
45     for(i=0;i<n;i++) if(sa[i]==len) break;
46     for(j=len;j<len+h[i+1];j++) printf("%c",s[j]+'a'-1);
47     if(h[i+1]) printf(" %d",h[i+1]);
48     else printf("0");    
49 }

④    旋转数字

大概题意:一串数字长度为n,可以把最后面的数移动到最前面(转圈),对于这样得到的n个数,求出 小于原数,等于原数,大于原数 的个数(需要去重)。比如,数123可以得到 312, 231, 123那么小于原数,等于原数,大于原数的各有一个。n<=100000

 样例输入:

341

样例输出:

1 1 1

这道题还是可以用后缀数组做,先复制一倍直接跑出sa和h,然后从头到尾扫一遍,如果h==len就不计入答案(去重),在sa[i]==0之前的是小于原串的,之后的是就是大于原串的。

下面代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 #define N 200010
 7 using namespace std;
 8 char s[N];
 9 int sa[N],xx[N],yy[N],wv[N],num[N],rk[N],h[N],n,m;
10 bool cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
11 void hou(){
12     int i,j,p,k=0,*x=xx,*y=yy;
13     for(i=0;i<m;i++) num[i]=0;
14     for(i=0;i<n;i++) num[x[i]=s[i]]++;
15     for(i=1;i<m;i++) num[i]+=num[i-1];
16     for(i=n-1;i>=0;i--) sa[--num[x[i]]]=i;
17     for(j=1,p=1;p<n;j*=2,m=p){
18         p=0;
19         for(i=n-j;i<n;i++) y[p++]=i;
20         for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
21         for(i=0;i<n;i++) wv[i]=x[y[i]];
22         for(i=0;i<m;i++) num[i]=0;
23         for(i=0;i<n;i++) num[wv[i]]++;
24         for(i=1;i<m;i++) num[i]+=num[i-1];
25         for(i=n-1;i>=0;i--) sa[--num[wv[i]]]=y[i];
26         swap(x,y),p=1,x[sa[0]]=0;
27         for(i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
28     }
29     for(i=0;i<n;i++) rk[sa[i]]=i;
30     for(i=0;i<n;h[rk[i++]]=k){for(k?k--:0,j=sa[rk[i]-1];s[i+k]==s[j+k];k++);}
31     return;
32 }
33 int main()
34 {
35     int i,len,pd,a1,a2;m=12;
36     scanf("%s",s);
37     n=len=strlen(s);
38     for(i=0;i<n;i++) s[n+i]=s[i];
39     n=strlen(s);
40     for(i=0;i<n;i++) s[i]=s[i]-'0'+1;
41     n++;hou();
42     pd=a1=a2=0;
43     for(i=0;i<n;i++){
44         if(h[i]>=len&&sa[i]!=0) continue;
45         if(sa[i]==0) pd=1;
46         else if(sa[i]<len){
47             if(pd==0) a1++;
48             else a2++;
49         }
50     }
51     printf("%d 1 %d",a1,a2);
52 }

⑤    匹配格式

大概题意:给定字符串S,找到字串E,使得S可以这样表示:EAEBE (A,B为任意字串),找出最长的E。S长度为10^6。

 样例输入:

aaxoaaaaa

样例输出:

2

呃...出了点问题...代码貌似错了...这份代码是求了最长的出现3次的字串...(这都能A数据是有多水....)

错误的代码先贴上来(改一下二分里的判断方式就行了吧):

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 #define N 1000010
 7 using namespace std;
 8 char s[N];
 9 int sa[N],xx[N],yy[N],wv[N],num[N],rk[N],h[N],n,m;
10 bool cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
11 void hou(){
12     int i,j,p,k=0,*x=xx,*y=yy;
13     for(i=0;i<m;i++) num[i]=0;
14     for(i=0;i<n;i++) num[x[i]=s[i]]++;
15     for(i=1;i<m;i++) num[i]+=num[i-1];
16     for(i=n-1;i>=0;i--) sa[--num[x[i]]]=i;
17     for(j=1,p=1;p<n;j*=2,m=p){
18         p=0;
19         for(i=n-j;i<n;i++) y[p++]=i;
20         for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
21         for(i=0;i<n;i++) wv[i]=x[y[i]];
22         for(i=0;i<m;i++) num[i]=0;
23         for(i=0;i<n;i++) num[wv[i]]++;
24         for(i=1;i<m;i++) num[i]+=num[i-1];
25         for(i=n-1;i>=0;i--) sa[--num[wv[i]]]=y[i];
26         swap(x,y),p=1,x[sa[0]]=0;
27         for(i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
28     }
29     for(i=0;i<n;i++) rk[sa[i]]=i;
30     for(i=0;i<n;h[rk[i++]]=k){for(k?k--:0,j=sa[rk[i]-1];s[i+k]==s[j+k];k++);}
31 }
32 bool check(int k){
33     int i,j,mx,mi,p=0,*y=yy;
34     for(i=0;i<n;i++){
35         if(h[i]<k){
36             if(p&&mx-mi>=k+k+2) for(j=0;j<p;j++) if(mx-y[j]>=k+1&&y[j]-mi>=k+1) return true;
37             p=0;
38             y[p++]=sa[i];
39             mi=mx=sa[i];
40         }
41         else{
42             mi=min(mi,sa[i]);
43             mx=max(mx,sa[i]);
44             y[p++]=sa[i];
45         }
46     }
47     return false;
48 }
49 int main()
50 {
51     int i,l,r,mid;m=28;
52     scanf("%s",s);
53     n=strlen(s);
54     for(i=0;i<n;i++) s[i]=s[i]-'a'+1;
55     n++;hou();
56     l=0,r=1000000;
57     while(l<r){
58         mid=(l+r)/2;
59         if(check(mid)) l=mid+1;
60         else r=mid;
61     }
62     printf("%d",l-1);
63 }

3.Trie树

这个很简单吧......

⑥ 糟糕的BUG

大概题意:有233333个字符串(单个长度不超过10,总长度不超过466666),查找有没有字符串是另外的字符串的前缀

样例输入1:

3

abc

abcdef

cdef

样例输出1:

Bug!

样例输入2:

3

abc

bcd

cde

样例输出2:

Good Luck!

这么简单的题...代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<cmath>
 6 using namespace std;
 7 struct node{
 8     int nxt[26];
 9     bool cnt;
10 }tree[500010];
11 int root=1,cnt=2,ans=0;
12 char s[20];
13 void insert(){
14     int pos,i,x,len=strlen(s);
15     pos=root;
16     for(i=0;i<len;i++){
17         x=s[i]-'a';
18         if(tree[pos].nxt[x]==0) tree[pos].nxt[x]=cnt++;
19         pos=tree[pos].nxt[x];
20     }
21     tree[pos].cnt=true;
22     return;
23 }
24 void dfs(int pos){
25     for(int i=0;i<26;i++){
26         if(tree[pos].cnt&&tree[pos].nxt[i]){ans=1;return;}
27         if(tree[pos].nxt[i]) dfs(tree[pos].nxt[i]);
28     }
29 }
30 int main()
31 {
32     int n,i;
33     scanf("%d",&n);
34     for(i=1;i<=n;i++){
35         scanf("%s",s);
36         insert();
37     }
38     dfs(root);
39     if(ans==1) printf("Bug!");
40     else printf("Good Luck!");
41 }

 

2018.01.19  --PnCbf

update 2018.02.03(火车上补博客...晃来晃去的...)

posted @ 2018-01-12 22:46  PnCbf  阅读(89)  评论(0编辑  收藏  举报