1. 程式人生 > >hiho 218 Keywords Filter(ac自動機解法+kmp優化解法)

hiho 218 Keywords Filter(ac自動機解法+kmp優化解法)

目錄

描述

輸入

輸出

題意分析:

2.思路

ac程式碼

題目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是什麼演算法的可以看我另一篇部落格

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