題意:給你幾個模式串,問你主串最少改幾個字符能夠使主串不包含模式串
思路:從昨天中午開始研究,研究到現在終於看懂了。既然是多模匹配,我們是要用到AC自動機的。我們把主串放到AC自動機上跑,並保證不出現模式串,這裏對AC自動機的創建有所改動,我們需要修改不存在但是符合要求的節點,如果某節點的某一子節點不存在,我們就把這個子節點指向他父輩節點存在的該節點(比如k->next[1]不存在,k->fail->next[1]存在,我們就把他改爲k->next[1] = k->fail->next[1]),因爲只是在AC自動機上跑,我們只關心會不會匹配到模式串。這裏要當心,如果一個節點的失配指向的是模式串尾,那麼這個節點也就是模式串尾。我們用dp[i][j]去儲存當前匹配到主串第i位,跑到AC自動機第j個狀態的最小修改,狀態轉移方程dp[i + 1][j-next] = min(dp[i][j->next],dp[i][j]+str[i]!=k),每次我們都從上一個狀態j開始搜索滿足要求的下一個狀態j->next。
參考:
[Pku 3691 1625] 字符串(四) {自動機應用}
代碼:
#include<cstdio>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define ll long long
const int maxn = 1000+5;
const int maxm = 100000+5;
const int MOD = 1e7;
const int INF = 0x3f3f3f3f;
const int kind = 4;
const char baset = 0;
using namespace std;
struct Trie{
Trie *next[kind];
Trie *fail;
int flag;
int id;
};
Trie *root,point[maxn];
queue<Trie*> Q;
int head,tail,idx;
int ID(char ch){
if(ch == 'A') return 0;
else if(ch == 'C') return 1;
else if(ch == 'T') return 2;
return 3;
}
Trie* NewNode(){
Trie *temp = &point[idx];
memset(temp ->next,NULL,sizeof(temp ->next));
temp ->flag = 0;
temp ->id = idx++;
temp ->fail = NULL;
return temp;
}
void Insert(char *s){
Trie *p = root;
for(int i = 0;s[i];i++){
int x = ID(s[i]);
if(p ->next[x] == NULL){
p ->next[x] = NewNode();
}
p = p ->next[x];
}
p ->flag = 1;
}
void del(Trie *p){
if(p == NULL) return;
for(int i = 0;i < kind;i++){
if(p ->next[i])
del(p ->next[i]);
}
delete p;
}
void buildFail(){
while(!Q.empty()) Q.pop();
Q.push(root);
Trie *p,*temp;
while(!Q.empty()){
temp = Q.front();
Q.pop();
for(int i = 0;i < kind;i++){
if(temp ->next[i]){
if(temp == root){
temp ->next[i] ->fail = root;
}
else{
p = temp ->fail;
while(p){
if(p ->next[i]){
temp ->next[i] ->fail = p ->next[i];
break;
}
p = p ->fail;
}
if(p == NULL) temp ->next[i] ->fail = root;
}
if(temp ->next[i] ->fail ->flag)
temp ->next[i] ->flag = 1;
//這裏要注意,如果一個節點的失配指向的是模式串尾
//那麼這個節點也就是模式串尾
Q.push(temp ->next[i]);
}
else if(temp == root)
temp ->next[i] = root;
//root指向root,因爲不存在這個子節點,沒有限制
else
temp ->next[i] = temp ->fail ->next[i];
//指向長輩節點存在的這個子節點
}
}
}
int dp[maxn][maxn << 1]; //主串匹配到第i個,在AC自動機上走到第j個節點
int solve(char *ch){
int len = strlen(ch);
for(int i = 0;i <= len;i++)
for(int j = 0;j <= idx;j++)
dp[i][j] = INF;
dp[0][0] = 0;
for(int i = 1;i <= len;i++){
for(int j = 0;j < idx;j++){ //從這個狀態j開始走,dp下一個狀態
if(point[j].flag) continue; //這個狀態包含模式串
if(dp[i - 1][j] == INF) continue;
for(int k = 0;k < 4;k++){
int r = point[j].next[k] ->id; //下一個點
if(point[r].flag) continue; //下一個點包含模式串
dp[i][r] = min(dp[i][r],dp[i - 1][j] + (ID(ch[i - 1]) != k));
}
}
}
int ans = INF;
for(int i = 0;i < idx;i++){
ans = min(ans,dp[len][i]);
}
if(ans == INF) return -1;
else return ans;
}
char ch[1005];
int main(){
int n,m,x,Case = 1;
while(scanf("%d",&n) != EOF && n){
idx = 0;
root = NewNode();
for(int i = 0;i < n;i++){
scanf("%s",ch);
Insert(ch);
}
buildFail();
scanf("%s",ch);
printf("Case %d: %d\n",Case++,solve(ch));
}
return 0;
}