IDDFS(迭代加深搜索)
IDDFS是Iterative Deepening DFS的缩写,是一种结合了DFS和BFS思想的搜索方法。具体操作为:设置每次DFS的深度depth,开始深度为1,每当没找到答案则depth++继续DFS,直到找到了答案。
每一层的广度上有着BFS的思想,但是实现是用DFS。
习题:埃及分数 LibreOJ - 10022
题意:给定一对分子分母a和b,求用单位分数来表示分数 a b \frac{a}{b} ba,单位分数即分子为1的分数。要求单位分数的个数最小,且分母不可相同,如果有多解,要求最小的分数是多组解中最大的。
题目没有明显的界限,那先想到我们第一层可以从 1 2 \frac{1}{2} 21开始,下一层 1 3 \frac{1}{3} 31,然后每一层分母都+1来进行尝试,对于同层也是分母+1然后向下搜索。显然这样的搜索十分盲目,有过多的浪费,因此我们必须得自己设定一定的搜索界限。
对于搜索下限:
假设到当前位置,剩余的分数为
x
y
\frac{x}{y}
yx,设下一个从
1
s
\frac{1}{s}
s1开始遍历,显然要求
x
y
≥
1
s
\frac{x}{y}\ge\frac{1}{s}
yx≥s1 即有
s
≥
x
y
s\ge\frac{x}{y}
s≥yx
另外考虑到分母不能相同,而且是从大向小的搜索,还要考虑上一个选择的分母+1是不是求得的s更大,如果更大则更新为上次分母+1。
对于搜索上限:
根据搜索的深度,我们可以求一个数k表示还能选几个分母,用e来表示上限,那么要满足
k
e
>
x
y
\frac{k}{e}>\frac{x}{y}
ek>yx(k>1),得到
e
<
k
y
x
e< \frac{ky}{x}
e<xky
借用这两个限制就很容易求得答案了
注意:这题的数据大概率会爆int
#include<cstdio>
using namespace std;
int n, pos, val[1010];//最多不超过1000层,val记录每一层得到的结果
bool ida(int now, int dep) {
if (now > dep)return false;
if (val[pos] << (dep - now) < n)return false;
if (val[pos] == n)return true;
pos++;
for (int i = 0; i < pos; i++) {
val[pos] = val[pos - 1] + val[i];
if (ida(now + 1, dep))return true;//相加
val[pos] = val[pos - 1] - val[i];
if (ida(now + 1, dep))return true;//相减
}
pos--;//失败则退回一步
return false;
}
int main(void) {
while (~scanf("%d", &n) && n) {
for (int dep = 1;; dep++) {
val[pos = 0] = 1;
if (ida(0, dep)) {//开始为0层
printf("%d\n", pos);
break;
}
}
}
return 0;
}
IDA*算法
IDA*算法能简要概述为:IDA* = IDDFS + 估价函数
在这里估价函数的作用是判断在某个深度时问题是否可解,若无解则返回,即IDDFS利用估价函数进行剪枝操作。
例题:Power Calculus POJ - 3134
题意:给定一个数n,让你从1开始进行加减,可以利用所有已有的结果,求最少步数得到n。
根据罗书上的说法:
IDDFS:指定递归深度,递归不超过这个深度。
估价函数:如果最快的方式计算(不断乘2),看当前值在规定深度的最大值是否小于n。
书上代码如下:
#include<cstdio>
using namespace std;
int n, pos, val[1010];//最多不超过1000层,val记录每一层得到的结果
bool ida(int now, int dep) {
if (now > dep)return false;
if (val[pos] << (dep - now) < n)return false;
if (val[pos] == n)return true;
pos++;
for (int i = 0; i < pos; i++) {
val[pos] = val[pos - 1] + val[i];
if (ida(now + 1, dep))return true;//相加
val[pos] = val[pos - 1] - val[i];
if (ida(now + 1, dep))return true;//相减
}
pos--;//失败则退回一步
return false;
}
int main(void) {
while (~scanf("%d", &n) && n) {
for (int dep = 1;; dep++) {
val[pos = 0] = 1;
if (ida(0, dep)) {//开始为0层
printf("%d\n", pos);
break;
}
}
}
return 0;
}
粗略的写写草稿发现贪心是不可行的。
然后想到4^len来构建字符串,如果盲目构造能有无穷多的结果,因此可以用到IDDFS,规定层数,设置其值开始为最长的字符串的长度。
接下来是构造h函数用于剪枝,h函数要求的是理想中的最优值,可以想到每个字符串中未匹配的最长长度即可为理想中的最优值,如果剩余可以构造的长度小于该值则返回。
#include<cstdio>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
const char ch[] = "ACGT";
int t, n, cnt[10];
string dna[10];
int h() {
int res = 0;
for (int i = 0; i < n; i++)
res = max(res, (int)dna[i].length() - cnt[i]);
return res;
}
bool ida(int now, int depth) {
if (now == depth + 1) {
if (!h())return true;
else return false;
}
if (depth - now + 1 < h())
return false;//当前这一层还没选,剩下可选的有depth-now+1个
int tmp[10] = { 0 };
for (int i = 0; i < n; i++)
tmp[i] = cnt[i];
for (int i = 0; i < 4; i++) {
int flag = 0;
for (int j = 0; j < n; j++)
if (dna[j][cnt[j]] == ch[i])
cnt[j]++, flag = 1;
if (flag) {
if (ida(now + 1, depth))return true;
else {
for (int j = 0; j < n; j++)
cnt[j] = tmp[j];
}
}
}
return false;
}
int main(void) {
scanf("%d", &t);
while (t--){
scanf("%d", &n);
int st = 0;
for (int i = 0; i < n; i++) {
cin >> dna[i];
if (dna[i].length() > st)
st = dna[i].length();
cnt[i] = 0;
}
int ans = 0;
for (int depth = st;; depth++) {
if (ida(1, depth)) {
ans = depth; break;
}
}
printf("%d\n", ans);
}
return 0;
}
②习题 The Rotation Game HDU - 1667
很显然像这种可以无限递归的能考虑IDA*
同样IDDFS设置每次的最大深度,从1开始。
H函数要考虑最理想的步数是多少,观察相邻的3个块,如122的形式,这种两个连续相同,假设其他5个块都匹配了2,那么最少1步即可;如121,123这种,两边和中间的不同,那必定得至少移动两次。因此构造出了一个比较低效的H函数,获得的值只有0,1,2三种情况。
万幸的是13s低空水过~,其实下列代码还能进行适当的常数优化,还有进步空间。
#include<cstdio>
#include<algorithm>
using namespace std;
int row1[7], row2[7], col1[7], col2[7];
int rowh1, rowh2, colh1, colh2, pos;
char ans[1000];
void print() {//测bug用的
printf("\n %d %d\n", col1[colh1], col2[colh2]);
printf(" %d %d\n", col1[(colh1 + 1) % 7], col2[(colh2 + 1) % 7]);
for (int i = 0; i < 7; i++)printf("%d", row1[(rowh1 + i) % 7]);
printf("\n %d %d\n", col1[(colh1 + 3) % 7], col2[(colh2 + 3) % 7]);
for (int i = 0; i < 7; i++)printf("%d", row2[(rowh2 + i) % 7]);
printf("\n %d %d\n", col1[(colh1 + 5) % 7], col2[(colh2 + 5) % 7]);
printf(" %d %d\n\n", col1[(colh1 + 6) % 7], col2[(colh2 + 6) % 7]);
}
int getH() {
int res = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
if (row1[(rowh1 + 3) % 7] != row1[(rowh1 + 2) % 7])t1++;
if (row1[(rowh1 + 3) % 7] != row1[(rowh1 + 4) % 7])t1++;
res = max(res, t1);
if (row2[(rowh2 + 3) % 7] != row2[(rowh2 + 2) % 7])t2++;
if (row2[(rowh2 + 3) % 7] != row2[(rowh2 + 4) % 7])t2++;
res = max(res, t2);
if (col1[(colh1 + 3) % 7] != col1[(colh1 + 2) % 7])t3++;
if (col1[(colh1 + 3) % 7] != col1[(colh1 + 4) % 7])t3++;
res = max(res, t3);
if (col2[(colh2 + 3) % 7] != col2[(colh2 + 2) % 7])t4++;
if (col2[(colh2 + 3) % 7] != col2[(colh2 + 4) % 7])t4++;
res = max(res, t4);
return res;
}
bool ida(int now,int depth) {
//printf("last=%c,pos=%d,now=%d,dep=%d,getH=%d\n", ans[pos - 1], pos, now, depth, getH());
//print();
if (getH() > depth - now)return false;
if (now == depth) {
if (!getH())return true;
else return false;
}
int tmp1, tmp2;
ans[pos++] = 'A';
colh1 = (colh1 + 1) % 7;
tmp1 = row1[(rowh1 + 2) % 7];
tmp2 = row2[(rowh2 + 2) % 7];
row1[(rowh1 + 2) % 7] = col1[(colh1 + 2) % 7];
row2[(rowh2 + 2) % 7] = col1[(colh1 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; colh1 = (colh1 + 6) % 7;
row1[(rowh1 + 2) % 7] = tmp1;
row2[(rowh2 + 2) % 7] = tmp2;
}
ans[pos++] = 'B';
colh2 = (colh2 + 1) % 7;
tmp1 = row1[(rowh1 + 4) % 7];
tmp2 = row2[(rowh2 + 4) % 7];
row1[(rowh1 + 4) % 7] = col2[(colh2 + 2) % 7];
row2[(rowh2 + 4) % 7] = col2[(colh2 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; colh2 = (colh2 + 6) % 7;
row1[(rowh1 + 4) % 7] = tmp1;
row2[(rowh2 + 4) % 7] = tmp2;
}
ans[pos++] = 'C';
rowh1 = (rowh1 + 6) % 7;
tmp1 = col1[(colh1 + 2) % 7];
tmp2 = col2[(colh2 + 2) % 7];
col1[(colh1 + 2) % 7] = row1[(rowh1 + 2) % 7];
col2[(colh2 + 2) % 7] = row1[(rowh1 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; rowh1 = (rowh1 + 1) % 7;
col1[(colh1 + 2) % 7] = tmp1;
col2[(colh2 + 2) % 7] = tmp2;
}
ans[pos++] = 'D';
rowh2 = (rowh2 + 6) % 7;
tmp1 = col1[(colh1 + 4) % 7];
tmp2 = col2[(colh2 + 4) % 7];
col1[(colh1 + 4) % 7] = row2[(rowh2 + 2) % 7];
col2[(colh2 + 4) % 7] = row2[(rowh2 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; rowh2 = (rowh2 + 1) % 7;
col1[(colh1 + 4) % 7] = tmp1;
col2[(colh2 + 4) % 7] = tmp2;
}
ans[pos++] = 'E';
colh2 = (colh2 + 6) % 7;
tmp1 = row1[(rowh1 + 4) % 7];
tmp2 = row2[(rowh2 + 4) % 7];
row1[(rowh1 + 4) % 7] = col2[(colh2 + 2) % 7];
row2[(rowh2 + 4) % 7] = col2[(colh2 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; colh2 = (colh2 + 1) % 7;
row1[(rowh1 + 4) % 7] = tmp1;
row2[(rowh2 + 4) % 7] = tmp2;
}
ans[pos++] = 'F';
colh1 = (colh1 + 6) % 7;
tmp1 = row1[(rowh1 + 2) % 7];
tmp2 = row2[(rowh2 + 2) % 7];
row1[(rowh1 + 2) % 7] = col1[(colh1 + 2) % 7];
row2[(rowh2 + 2) % 7] = col1[(colh1 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; colh1 = (colh1 + 1) % 7;
row1[(rowh1 + 2) % 7] = tmp1;
row2[(rowh2 + 2) % 7] = tmp2;
}
ans[pos++] = 'G';
rowh2 = (rowh2 + 1) % 7;
tmp1 = col1[(colh1 + 4) % 7];
tmp2 = col2[(colh2 + 4) % 7];
col1[(colh1 + 4) % 7] = row2[(rowh2 + 2) % 7];
col2[(colh2 + 4) % 7] = row2[(rowh2 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; rowh2 = (rowh2 + 6) % 7;
col1[(colh1 + 4) % 7] = tmp1;
col2[(colh2 + 4) % 7] = tmp2;
}
ans[pos++] = 'H';
rowh1 = (rowh1 + 1) % 7;
tmp1 = col1[(colh1 + 2) % 7];
tmp2 = col2[(colh2 + 2) % 7];
col1[(colh1 + 2) % 7] = row1[(rowh1 + 2) % 7];
col2[(colh2 + 2) % 7] = row1[(rowh1 + 4) % 7];
if (ida(now + 1, depth))return true;
else {
pos--; rowh1 = (rowh1 + 6) % 7;
col1[(colh1 + 2) % 7] = tmp1;
col2[(colh2 + 2) % 7] = tmp2;
}
return false;
}
int main(void) {
while (~scanf("%d", &col1[0]) && col1[0]){
scanf("%d %d %d", &col2[0], &col1[1], &col2[1]);
for (int i = 0; i < 7; i++)scanf("%d", &row1[i]);
col1[2] = row1[2]; col2[2] = row1[4];
scanf("%d %d", &col1[3], &col2[3]);
for (int i = 0; i < 7; i++)scanf("%d", &row2[i]);
col1[4] = row2[2]; col2[4] = row2[4];
scanf("%d %d %d %d", &col1[5], &col2[5], &col1[6], &col2[6]);
rowh1 = rowh2 = colh1 = colh2 = pos = 0;
int dep = 0;
while (!ida(0, dep))dep++;
if (dep)for (int i = 0; i < dep; i++)printf("%c", ans[i]);
else printf("No moves needed");
printf("\n%d\n", row1[(rowh1 + 2) % 7]);
}
return 0;
}
总结
总的来说,A*类算法其实就是在遍历的过程中加入合适的剪枝,从而引导遍历方向向着离答案近的方向进行,因此说白了就是一种剪枝法,但是这类算法有一定的模式可套,即每次以最理想的情况来判断是否剪枝,更容易想到代码的实现方式。