[Usaco2015 Feb] [Bzoj3942] Censoring

1.KMP版Censoring

 题干:

  给出两个字符串 S 和 T,每次从前往后找到 S 的一个子串 A=T 并将其删除,空缺位依次向前补齐,重复上述操作多次,直到 S 串中不含 T 串。输出最终的 S 串。

 题解:

  这是一道KMP入门题,像题目描述一样,KMP最主要的应用就是进行单模式匹配(给出一个母串与一个目标串进行匹配)。其实KMP的核心只有一个Next[]数组。Next[]表示的是目标串的一个节点,这个目标串的节点及以前的部分 与 母串的某一部分一一对应,而那个母串的某一部分的最后一位就是Next[]的下标。因为本题还涉及到不断删除与拼接字符串,一开始我就想先删一遍,然后接上字符串后再删一遍,如此重复。最后是又WA又TLE。。。其实应该将或匹配或不匹配的节点压入栈中,当匹配的数量正好为目标串的长度时,弹掉栈的头几个(目标串长度个)并拿出栈顶继续匹配(节省了重匹配的时间,也方便了最后的弹栈输出)。Get it!

 Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 1111111
 4 using namespace std;
 5 char a[$],s[$];
 6 int Next[$],judge[$],m,n,k,t,lengths,lengtha,q[$],up;
 7 //Next[i]定义的是在1——i中,最长的既是前缀又是后缀的长度 
 8 signed main(){
 9     scanf("%s%s",s+1,a+1);
10     lengths=strlen(s+1); lengtha=strlen(a+1);
11     Next[1]=0;
12     for(register int i=1,nex=0;i<=lengtha;++i){//
13         while(nex!=0&&a[i]!=a[nex]) nex=Next[nex];
14         Next[i+1]=++nex;
15     }//预处理出小串的 Next,失配时返回可匹配的最长串 
16     for(register int i=1;i<=lengtha;++i) printf("%d ",Next[i]); 
17     for(register int i=1,nex=1;i<=lengths;++i){
18         while(nex!=0&&s[i]!=a[nex]) nex=Next[nex];
19         //如果失配,跳到上一次出现已匹配到串的位置 
20         nex++;//跳到下一位 
21         q[++up]=i;//压栈 
22         judge[up]=nex;
23         if(nex==lengtha+1) up-=lengtha,nex=judge[up]; 
24         //如果匹配成功(nex已经++过了,所以要等于 lengtha+1)
25         //跳栈并返回上一次匹配位置 
26     }
27     for(register int i=1;i<=up;++i) printf("%c",s[q[i]]);
28     return 0;
29 }
Code

 

2.AC自动机版Ceosoring

题干:

  FJ把杂志上所有的文章摘抄了下来并把它变成了一个长度不超过10^5的字符串S,他有一个包含 n 个单词的列表,列表里的n个单词记为t1----tn,他希望从S中删除这些单词。FJ每次在 S 中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从S中删除这个单词。他重复这个操作直到S中没有列表里的单词为止。注意删除一个单词后可能会导致 S 中出现另一个列表中的单词,FJ注意到列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在S中出现的开始位置是互不相同的。请帮助 FJ 完成这些操作并输出最后的 S。

题解:

  AC自动机相对于KMP只是将单模式匹配转变为多模式匹配(就是目标串多了),由Next[]变为fail[]而已。同样是不断压栈,弹栈,最后倒序输出。在trie树(图)上,可以理解为母串挂在这棵树上跑。具体AC自动机内容点这里

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>// next 为保留字!!!!!!!!!!! 
 4 #define $ 111111
 5 using namespace std;
 6 char s[$],ss[$];
 7 int m,n,k,tot,length;
 8 struct trie{    int size; trie *fail,*son[27];    }*sta[$*1000];
 9 queue<trie*> q;
10 inline trie *newnode(){
11     trie *p=new trie;
12     p->size=0; p->fail=NULL;
13     for(register int i=0;i<=25;++i) p->son[i]=NULL;
14     return p;
15 }
16 inline void insert(trie *root){
17     trie *p=root;
18     length=strlen(ss+1);
19     for(register int i=1;i<=length;++i){
20         int x=ss[i]-'a';
21         if(p->son[x]==NULL) p->son[x]=newnode();
22         p=p->son[x];
23     }
24     p->size=length;//维护的是子串长度 
25 }
26 inline void build(trie *root){// trie图 
27 //    q.push(root);    //只需直接维护儿子,不可将 root 入队 
28     for(register int i=0;i<=25;++i){
29         if(root->son[i]==NULL) root->son[i]=root;//周围一堆儿子 
30         else root->son[i]->fail=root,q.push(root->son[i]);
31     }
32     while(q.size()){
33         trie *p=q.front();  q.pop();
34         for(register int i=0;i<=25;++i){
35             if(p->son[i]!=NULL){
36                 p->son[i]->fail=p->fail->son[i];
37                 q.push(p->son[i]);
38             }
39             else p->son[i]=p->fail->son[i];//将 trie 树补全 
40         }
41     }
42 }
43 signed main(){
44     trie *root=newnode();
45     scanf("%s%d",s+1,&n);
46     for(register int i=1;i<=n;++i) scanf("%s",ss+1),insert(root);
47     build(root);  
48     sta[0]=root;//当在过程中栈被跳空,需有一个 root 缓冲 
49     length=strlen(s+1);//注意:strlen 时间复杂度 O(n) 不是O(1) 
50     for(register int i=1;i<=length;++i){
51         int x=s[i]-'a';
52         root=root->son[x];//在trie树上跑,找相应单词
53         //若没有,就为 root(NULL) 
54         sta[++tot]=root; ss[tot]=s[i];//标记并压栈 
55         if(root->size) tot-=root->size,root=sta[tot];
56         //如果匹配成功,跳回此单词出现位置之前,但 i 不变 
57         //将 root 置为以前状态 
58     }
59     for(register int i=1;i<=tot;++i) printf("%c",ss[i]);
60 }
Code

 

3.Hash版Censoring

题干:(同第二题)

  FJ把杂志上所有的文章摘抄了下来并把它变成了一个长度不超过10^5的字符串S,他有一个包含 n 个单词的列表,列表里的n个单词记为t1----tn,他希望从S中删除这些单词。FJ每次在 S 中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从S中删除这个单词。他重复这个操作直到S中没有列表里的单词为止。注意删除一个单词后可能会导致 S 中出现另一个列表中的单词,FJ注意到列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在S中出现的开始位置是互不相同的。请帮助 FJ 完成这些操作并输出最后的 S。

题解:

  Hash其实就是将一个字符串压成一个数,在比对时像二进制左移右移一样调整位置(在Hash中往往是一个适当大小的质数进制,防止两段字符串不相等但Hash值相等的事故)。

Hash值因为可以减小O(len)的时间复杂度而在实际应用(骗分)中大受欢迎。。。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 200010
 4 #define ull unsigned long long
 5 #define zzyy 13131
 6 using namespace std;
 7 int lens,n,m,len[$],istack[$*100],top;
 8 ull hashss[$],prime[$],hstack[$*100];
 9 char s[$],ss[$];
10 signed main(){
11     prime[0]++;
12     for(register int i=1;i<=100001;++i) prime[i]=prime[i-1]*zzyy;//预处理 
13     scanf("%s%d",s+1,&n);  lens=strlen(s+1);
14     for(register int i=1;i<=n;++i){
15         scanf("%s",ss+1);
16         len[i]=strlen(ss+1);
17         for(register int j=1;j<=len[i];++j){
18             hashss[i]=hashss[i]*zzyy+ss[j]-'a'+1;
19             //更新每一个串的hash值,压成一个点与主串比较 
20         }
21     }
22     for(register int i=1;i<=lens;++i){
23         istack[++top]=i;
24         hstack[top]=hstack[top-1]*zzyy+s[i]-'a'+1;//正常每位压 hash 
25         for(register int j=1;j<=n;++j){
26             if(top-len[j]<0) continue;
27             if(hstack[top]-hstack[top-len[j]]*prime[len[j]]==hashss[j]){
28                 top-=len[j]; break;//比较 
29             }
30         }
31     }
32     for(register int i=1;i<=top;++i) putchar(s[istack[i]]);
33 }
Code

 

posted @ 2019-06-30 15:42  OI_zzyy  阅读(311)  评论(1编辑  收藏  举报