IDA*、剪枝、较难搜索、扫描——DNA sequence HDU - 1560
翻译
题意就是给出N个DNA序列,要求出一个包含这n个序列的最短序列是多长
这是一道搜索题,为什么呢?从样例可以感受到,我们应该从左往右“扫描”,从n个DNA序列中取出某个特定的字母,直到n个序列都被取空。题目便是要求这个“取出”次数的最小值。而我们每次都选择A,T,C,G中的其中一个取出,这样便形成了一个不断以4个叶子延申的搜索树了。(请自行脑补)(当然实际必须要剪掉)
IDA*
这题很适合用IDA*,随着深度不断增加,第一个访问到的答案一定是最优解。而庞大的数据,也需要我们设计一个评估函数。
剪枝
我们使用评估函数来进行剪枝。很自然可以想到,我们用已经处理完的长度,加上估计还需要的长度,它如果大于给定的长度,那么这种方法就是不可行的,那么我们就可以退出。
具体而言,有两种方法可以进行待处理长度的预测。一种是计算当前各序列未就位的序列的最大长度;另外一种是统计各个字母在所有序列的未就位的序列中的最大数量之和
实践表明,后者是更优的。
布尔型的函数
对于返回值的理解,首先这个dfs函数是布尔型的,剪枝即判错,其它情况下,返回值则是下一层的子函数的返回值,下一层的返回值又是由下下一层的返回值决定的……不断这样递归下去,当找到正确答案时那就会不断返回true直至给第一个母函数啦。具体而言,首先在dfs的开头要剪枝,然后用一个循环对不同操作再次进行搜索,这若干个操作之中若有一个为真,那么这个函数就会返回真;只有当他们全部返回假,当前这个函数才会返回假。
过程中的重大错误
- 把int类型的eva函数写成了bool,导致所有非0的数都变成了 1
- 忘记给递归函数的某些路径设置返回值,导致函数返回异常值,明明是bool类型的函数,居然返回了4。。。影响了后续的判断(我当时还忽略了编译器的友情提醒:不是所有的路径都返回值...)
- 当有多次操作时,要注意状态的初始化,包括各种数组与一些最大最小值等任何一个过程中需要的变量都要检查。
代码
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int T, n;//T是总问题数,n是每个问题里面有多少个DNA序列
struct node {
int len;
string str;
}dna[10];
char part[5] = { 'A', 'T', 'G', 'C' };//扫描的顺序,不一定是这个,只是说按我这个顺序来进行操作然后接着dfs
int pos[10] = { 0 };//记录第i个字符串扫描到了第几位
int eva() {
int maxn = -1;
for (int i = 0; i < n; i++) {
maxn = max(maxn, dna[i].len - pos[i]);
}
return maxn;
}
int eva2() {//比第eva快了二十倍
int ans = 0;
int cnt[4] = { 0 };
int maxn[4] = { 0 };
for (int i = 0; i < n; i++) {
int a = 0, b = 0, c = 0, d = 0;
for (unsigned int j = pos[i]; j < dna[i].str.length(); j++) {
if (dna[i].str[j] == 'A') cnt[0]++;
if (dna[i].str[j] == 'T') cnt[1]++;
if (dna[i].str[j] == 'G') cnt[2]++;
if (dna[i].str[j] == 'C') cnt[3]++;
}
for (int k = 0; k < 4; k++) {
maxn[k] = max(maxn[k], cnt[k]);
cnt[k] = 0;
}
}
for (auto i : maxn)
ans += i;
return ans;
}
bool IDA(int now, int dep) {
int e = eva2();
//printf("e is %d, now is %d, dep is %d\n", e, now, dep);
if (now > dep) return false;//最后一个dfs返回false的条件
if (now + e > dep) return false;//剪枝
if (e == 0) return true;////最后一个dfs返回true的条件
//这函数应该是bool类型的吗?这样只有某一个子函数会为真吧?那最开始的那个呢?---不断递归,不断返回
//整个函数返回true的条件是什么,应该不是放在开头吧---开头的可以保证最后一个dfs返回真,结尾的可以保证过程中的dfs返回真(false同理)
int tep[10] = { 0 };
memcpy(tep, pos, sizeof(tep));
for (int i = 0; i < 4; i++) {
int flag = 0;
for (int j = 0; j < n; j++) {
// printf("i is %d, j is %d\n", i, j);
if (dna[j].str[pos[j]] == part[i]) {
pos[j]++;
flag = 1;
}
}
/*
for (int j = 0; j < n && flag; j++)
printf("pos[%d] is %d\n", j, pos[j]);
cout << endl;
*/
if (flag) {
if (IDA(now + 1, dep))
return true;
}
memcpy(pos, tep, sizeof(pos));
}
return false;//没有这一句递归就无法进行,为什么?!
}
int main(void) {
cin >> T;
while (T--) {
cin >> n;
memset(pos, 0, sizeof(pos));
int maxn = 1;
for (int i = 0; i < n; i++) {
cin >> dna[i].str;
dna[i].len = dna[i].str.length();
maxn = max(dna[i].len, maxn);
}
int dep;
for (dep = maxn;; dep++) {
memset(pos, 0, sizeof(pos));
if (IDA(0, dep)) break;
}
cout << dep << endl;
for (int i = 0; i < n; i++) {
dna[i].str.clear();
dna[i].len = 0;
}
}
return 0;
}