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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值