题意:
给定m个模式串和一个匹配串,只包含字符ACGT,
一次操作你可以修改匹配串的一个字符(只能修改为ACGT中的一种),
问最少进行多少次操作,能使得匹配串不包含任意模式串,
输出最少操作次数,如果无解输出-1。
数据范围:m<=50,|每个模式串|<=20,|匹配串|<=1000
解法:
对模式串建立ac自动机,
设匹配串的长度为位n
那么问题变为在ac自动机上走n步,不能走到任意模式串的结尾处.
如果按照匹配串的字符走,那么不需要代价,如果不按照匹配串的字符走,那么代价为1.
那么就是在合法情况下(不走到模式串结尾),求走n步的最小代价.
令d[i][j]表示走i步,当前在节点j上,的最小代价
转移方程:
设下一步走k,下一个节点为nt[j][k],设nt[i][j]=x
d[i+1][x]=min(d[i+1][x],d[i][j]+(s[i+1]和字符k不同) )
在ac自动机上dp即可.
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e3+5;
char s[maxm];
int n,m;
int get(char c){
if(c=='A')return 0;
if(c=='C')return 1;
if(c=='G')return 2;
return 3;
}
struct AC{
int a[maxm][4],fail[maxm],val[maxm],tot=0;
void init(){
for(int i=0;i<=tot;i++){
memset(a[i],0,sizeof a[i]);
val[i]=fail[i]=0;
}
tot=0;
}
void add(char *s){
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=get(s[i]);
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
val[node]++;
}
void build(){//构造fail
queue<int>q;
for(int i=0;i<4;i++){
if(a[0][i]){
q.push(a[0][i]);
}
}
while(!q.empty()){
int x=q.front();
q.pop();
val[x]+=val[fail[x]];
for(int i=0;i<4;i++){
if(a[x][i]){
fail[a[x][i]]=a[fail[x]][i];
q.push(a[x][i]);
}else{
a[x][i]=a[fail[x]][i];
}
}
}
}
//
int d[1005][maxm];//d[i][j]表示走i步,当前在j节点上的最小代价.
int solve(){
//init
for(int i=0;i<=n;i++){
for(int j=0;j<=tot;j++){
d[i][j]=1e9;
}
}
//dp
d[0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=tot;j++){
if(d[i][j]==1e9)continue;
int now=get(s[i+1]);
for(int k=0;k<4;k++){
int x=a[j][k];
if(val[x])continue;
d[i+1][x]=min(d[i+1][x],d[i][j]+(k!=now));
}
}
}
//
int ans=1e9;
for(int j=0;j<=tot;j++){
ans=min(ans,d[n][j]);
}
if(ans==1e9)ans=-1;
return ans;
}
}ac;
signed main(){
int cas=1;
while(scanf("%d",&m)!=EOF&&m){
ac.init();
for(int i=1;i<=m;i++){
scanf("%s",s);
ac.add(s);
}
ac.build();
//
scanf("%s",s+1);
n=strlen(s+1);
int ans=ac.solve();
printf("Case %d: ",cas++);
printf("%d\n",ans);
}
return 0;
}