hiho 218 Keywords Filter(ac自動機解法+kmp優化解法)
目錄
題目1 : Keywords Filter
時間限制:10000ms
單點時限:1000ms
記憶體限制:256MB
描述
You are given a string S which consists of only lower case letters and N keyword W1, W2, ... WN which also consists of only lower case letters. For each appearance of a keyword in S you need to change the letters matching the keyword into '*'.
Assume S = "abcxyzabcd" and keywords are {"abc", "cd"}, the filtered S' will be "***xyz****".
輸入
The first line contains an integer N. (1 <= N <= 1000)
The following N lines each contains a keyword. The total length of the keywords is no more than 100000.
The last line contains the string S. The length of S is no more than 100000.
輸出
The filtered string.
樣例輸入
2
abc
cd
abcxyzabcd
樣例輸出
***xyz****
題意分析:
1.題是什麼?
給你1000個長度為100000以內的遮蔽詞,然後給你一段長度100000以內的文章,要把文章中所有遮蔽詞都換成'*'號然後輸出.
2.思路
(1)ac自動機?單純kmp?
字串多模匹配,妥妥的ac自動機模版題的感覺啊,不過我個人有點沒搞懂ac自動機的fail指標那個地方而且很久沒寫ac自動機了,故而ac自動機的解法後面會補上,下面放我只用kmp配合一些優化成功ac的解法:
首先不知道kmp是什麼演算法的可以看我另一篇部落格
(2)單純kmp時間空間複雜度分析
首先分析解法的時間複雜度,我們知道kmp時間複雜度是m+n的,故而如果單純的用kmp,預計時間複雜度會是在1000*(100000+100000),也就是2億,時間上勉強能接受,空間複雜度上需要先存下那1000個關鍵字,最極限需要1億個char空間,空間上很懸然而我這樣存沒出事,看來輸入是手下留情了的.
(3)解法以及關鍵優化
解法是以對每個遮蔽詞以kmp做匹配,匹配成功則遮蔽相應段,然而單純這麼做只能過90%資料,因為這麼做存在時間複雜度暴漲的特例:
對於文章aaaaaaaaaaaaaaaaaaaaaa,遮蔽詞aaaaaa在文章中做kmp會多次匹配成功,每次都傻傻的遮蔽相應段會造成時間複雜度暴漲為(m-n)*n,幾乎是m*n,故而我們需要做一下優化:
導致時間複雜度暴漲的關鍵是我們重複遮蔽了某些段,故而我們只要做一個lastpingbi存最後遮蔽的地方,迴圈時以lastpingbi為輔助邊界,就可以避免重複遮蔽,成功ac
(4)ac自動機做法
不知道什麼是ac自動機?參考我這篇部落格吧 ac自動機詳解
其實用ac自動機就做成了模版題,自己將第三步模式匹配那裡改一下,匹配成功時記錄一下修改區間即可,程式碼如下,
關於區間合併那裡用到了一個小技巧,見此: 快速區間合併
ac程式碼
1.單純kmp
#include <iostream>
using namespace std;
const int maxn=100005;
string keyword[1000];
int ntext[maxn];//ntext陣列
void getntext(string s){//構造ntext陣列,真正的模版
ntext[0]=-1;
int i=0,j=-1; //j為什麼初值賦值-1?0其實也行,僅僅是為了少一個判斷,
while(s[i]){
if(j==-1||s[i]==s[j]) ntext[++i]=++j;
else j=ntext[j];
}
}
int lastpingbi; //這個非常重要,沒有這個只能過90%資料
string wordsfilter(string a,string b,string ans){
lastpingbi=0;
int blen=0;
while(b[blen]) blen++;
getntext(b);
int i=0,j=0;
while(a[i]){
if(j==-1||a[i]==b[j]){
i++,j++;
if(!b[j]){
for(int l=i-1;l>=std::max(lastpingbi,i-blen);l--) ans[l]='*';
lastpingbi=i-1;
j=ntext[j];
}
}
else j=ntext[j];
}
return ans;
}
void solve(){
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>keyword[i];
string str,ans;
cin>>str;
ans=str;
for(int i=0;i<n;i++) ans=wordsfilter(str,keyword[i],ans);
cout<<ans<<endl;
}
int main(){
solve();
return 0;
}
2.ac自動機
#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
#include <malloc.h>
using namespace std;
const int maxkeywordlen=100001;
const int maxstrlen=100001;
char keyword[maxkeywordlen],str[maxstrlen],ans[maxstrlen];
int vis[maxstrlen];
int charmapping[256]; //字元對映陣列,charmapping[i]=x表示ascii碼為i的字元對應於treenode中的next[x]
void init_charmapping(){
for(int i='a';i<='z';i++){ //我的這個字典樹現在只允許輸入小寫字元組成的字串,然而由於有charmapping的存在,增加新字元新增對映並且增大maxn就好,很方便.
charmapping[i]=i-'a';
}
}
struct node{
node *fail; //失敗指標,此節點失配時將跳向fail所指節點
int end; //此值表示以此節點為結尾的單詞個數
node *next[26]; //指向子節點
}root;
node *getnode(){ //構造 一個新結點並做好初始化,返回指向該節點的指標
node *newnode=(node *)malloc(sizeof(node));
newnode->end=0;
newnode->fail=NULL;
for(int i=0;i<26;i++) newnode->next[i]=NULL;
return newnode;
}
void insert(char *s){ //向ac自動機加入字串s
int k=0,temp;
node* t=&root;
for(int i=0;s[i];i++){
temp=charmapping[s[i]];
if(!t->next[temp]) t->next[temp]=getnode();
t=t->next[temp];
}
t->end=strlen(s);
}
void build_fail(){ //構造ac自動機的失敗指標
queue<node *> q;
q.push(&root);
while(!q.empty()){
node* t=q.front();
q.pop();
for(int i=0;i<26;i++){
if(t->next[i]){
if(t==&root) t->next[i]->fail=&root;
else{
node *p=t->fail;
while(p!=NULL){
if(p->next[i]){
t->next[i]->fail=p->next[i];
break;
}
p=p->fail;
}
if(p==NULL) t->next[i]->fail=&root;
}
q.push(t->next[i]);
}
}
}
}
void match(char *s){ //計算ac自動機中所有模式串在字串s中出現的次數之和
int slen=strlen(s);
for(int i=0;i<slen;i++) vis[i]=0;
node *p=&root;
for (int i=0;i<slen;++i){
while(p!=&root&&p->next[charmapping[s[i]]]==NULL) p=p->fail;
if (p->next[charmapping[s[i]]]!=NULL)
{
p =p->next[charmapping[s[i]]] ;
if (p->end){
vis[i-p->end+1]++;
vis[i+1]--;
}
else{
node *tn=p->fail;
while(!tn->end&&tn!=&root){
tn=tn->fail;
}
if(tn->end){
vis[i-tn->end+1]++;
vis[i+1]--;
}
}
}
}
for(int i=1;i<slen;i++) vis[i]+=vis[i-1];
for(int i=0;i<slen;i++) printf("%c",vis[i]>0?'*':s[i]);
}
int main(){
init_charmapping();
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",&keyword);
insert(keyword);
}
build_fail();
scanf("%s",&str);
match(str);
return 0;
}