POJ 3691 DNA repair (AC自动机+dp)

题目类型 AC自动机+dp

题目意思
给出最多 50 个最长 20 的不好的字符串 问给出一个最长 1000 的字符串至少要修改多少个字符才可以使这个字符串不包含不好的字符串
字符串只含有(A,G,C,T)这四种字符

解题方法
用输入的 不好的字符串 构造ac自动机 (结点数不会超过 1000) 然后进行普通的匹配过程, 简单地理解为在AC自动机上进行移动
需要注意的地方
1.不能移动到那些如果走到这里意味着已经和某不好的串匹配成功了的 状态结点
2.由于可以修改字符所以可以移动到原本不能移动到的 状态结点 但是 cost 要 +1
仔细想下可以发现 前 i 个字符的解可以由前 i-1 个字符得到的解推出来 即可用 dp 解
dp[i][j] 使前i个字符不包含不好的字符串且最终落在 状态结点 j 上的最少修改次数
dp[i+1][child[j]] = dp[i][j] + ( edge(j,child[j]) != str(i+1) ) (其中 edge(j,child[j])的意思是 j 结点到 child[j] 结点这条边代表的字符,如果这个字符与当前正在匹配的 str(i+1)字符不等的话显然就要把 str(i+1) 修改成该字符 str(i+1)指要修改到满足要求的那个字符串的第i+1个字符 相等的话直接转移即可)

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>

using namespace std;

#define B printf("BUG!!\n");
const int INF = 1<<29;

char str[60][30];
char s[1100];
int dp[1100][1100];
int f[1100];
int last[1100];
bool vis[1100];

struct Trie {
	int ch[1100][30];
	int val[1100];
	int sz;
	int n_kind;

	void init() {
		n_kind = 4;
		sz = 1;
		memset(ch[0], 0, sizeof(ch[0]));
		memset(val, 0, sizeof(val));
		memset(last, 0, sizeof(last));
	}

	int mp(char c) {
		if(c == 'A') return 0; if(c == 'G') return 1;
		if(c == 'C') return 2; return 3;
	}

	void insert(char * s) {
		int len = strlen(s);
		int u = 0;
		for( int i=0; i<len; i++ ) {
			int id = mp(s[i]);
			if(ch[u][id] == 0) {
				memset(ch[sz], 0, sizeof(ch[sz]));
				ch[u][id] = sz++;
			}
			u = ch[u][id];
		}
		val[u] = 1;
	}

	void build_ac() {
		queue<int>q;
		f[0] = 0;
		for( int i=0; i<n_kind; i++ ) {
			if(ch[0][i]) {
				f[ch[0][i]] = 0;
				q.push(ch[0][i]);
			}
		}
		while(!q.empty()) {
			int u = q.front(); q.pop();
			for( int i=0; i<n_kind; i++ ) {
				if(ch[u][i] == 0) {
					ch[u][i] = ch[f[u]][i];
					continue;
				}
				int tu = u;
				while(tu && ch[f[tu]][i] == 0) tu = f[tu];
				f[ch[u][i]] = ch[f[tu]][i];
				last[ch[u][i]] = val[ch[f[tu]][i]] ? ch[f[tu]][i] : last[ch[f[tu]][i]];
				q.push(ch[u][i]);
			}
		}
	}

	int solve() {
		int len = strlen(s);
		int ans = INF;
		for( int i=0; i<=len; i++ ) for( int j=0; j<sz; j++ ) dp[i][j] = INF;
		dp[0][0] = 0;
		for( int i=1; i<=len; i++ ) {
			int id = mp(s[i-1]);
			for( int j=0; j<sz; j++ ) {
				for( int k=0; k<n_kind; k++ ) {
					int next = ch[j][k];
					if(last[next] || val[next]) continue;
					dp[i][next] = min(dp[i][next], dp[i-1][j] + (id != k));
					if(i == len) ans = min(ans, dp[i][next]);
				}
			}
		}
		return ans;
	}

	/*void count() {
		int len = strlen(s), u = 0;
		int cnt = 0;
		for( int i=0; i<len; i++ ) {
			int id = s[i] - 'A';
			u = ch[u][id];
			if(val[u]) {
				printf("i = %d\n", i+1);
				cnt++;
				int tu = u;
				while(last[tu]) { cnt++; tu = last[tu]; }
			}
		}
		printf("%d\n", cnt);
	}*/
}trie;

int main() {
	freopen("in2", "r", stdin);
	int n, cnt = 1;
	while(scanf("%d", &n), n) {
		trie.init();
		for( int i=0; i<n; i++ ) { scanf("%s", str[i]); trie.insert(str[i]); }
		trie.build_ac();
		scanf("%s", s);
		int ans = trie.solve();
		printf("Case %d: ", cnt++);
		if(ans == INF) printf("-1\n");
		else printf("%d\n", ans);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值