[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 }
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 }
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 }