目录
迭代加深搜索
迭代加深搜索(IDDFS)是一种结合了DFS和BFS思想的搜索方法。当搜索树很深且很宽的时候,用DFS会陷入递归无法返回,用BFS队列空间会爆炸,那么可以试试IDDFS,简单来说,就是每次限制搜索深度的DFS,在层数上采用BFS思想来逐步扩大DFS的搜索深度。
IDA*算法核心
寻找可达的最大深度deep,若没有到达目标状态则加深最大深度。当找到解需要的至少层数+当前层数>层数限制时,直接退出。
估价函数对迭代加深搜索的优化,即乐观估计剪枝估。估计至少还要多少步才能出解。设深度上限为maxd,当前结点n的深度为g(n),乐观估价函数为h(n),则当g(n)+h(n)>maxd时应该剪枝。
例1.埃及分数
在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。
如:19/45=1/3 + 1/12 + 1/180
19/45=1/3 + 1/15 + 1/45
19/45=1/3 + 1/18 + 1/30,
19/45=1/4 + 1/6 + 1/180
19/45=1/5 + 1/6 + 1/18.
最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
给出a,b(0<a<b<1000),编程计算最好的表达方式。
样例输入
19 45
样例输出
5 6 18
思路
(1)枚举深度deep。没有指明等式数目,dfs搜索没有上限,所以需要枚举深度,直到找到跳出即可.
(2) 深度上限deep还可以用来“剪枝”。 按照分母递增的顺序来进行扩展,如果扩展到i层时,前i个分数之和为,而第i个分数为,则接下来至少还需要个分数,总和才能达到。
AC代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 100 + 5;
typedef long long LL;
LL v[maxn], ans[maxn];
int a, b, deep;
LL gcd(LL a, LL b)
{
return b == 0 ? a : gcd(b, a%b);
}
// 返回满足1/c <= a/b的最小c
inline int get_first(LL a, LL b)
{
return b/a+1;
}
// 如果当前解v比目前最优解ans更优,更新ans
bool better(int d)
{
for(int i = d; i >= 0; i--)
if(v[i] != ans[i])
{
return ans[i] == -1 || v[i] < ans[i];
}
return false;
}
// 当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d, int from, LL aa, LL bb)
{
if(d == deep)
{
if(bb % aa)//分子(aa)必须是1
return false; // aa/bb必须是埃及分数
v[d] = bb/aa;
if(better(d))//内存拷贝
memcpy(ans, v, sizeof(LL) * (d+1));
return true;
}
bool ok = false;
from = max(from, get_first(aa, bb)); // 该层枚举的起点
for(int i = from; ; i++)
{
// 剪枝:如果剩下的deep+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解
if(bb * (deep+1-d) <= i * aa)
break;
v[d] = i;
// 计算aa/bb - 1/i,设结果为a2/b2
LL b2 = bb*i;
LL a2 = aa*i - bb;
LL g = gcd(a2, b2); // 以便约分
if(dfs(d+1, i+1, a2/g, b2/g))
ok = true;
}
return ok;
}
int main()
{
int kase = 0;
while(cin >> a >> b)
{
int ok = 0;
for(deep = 1; deep<= 100; deep++)
{
//从小到大枚举深度上限maxd,每次执行只考虑深度不超过maxd的结点
memset(ans, -1, sizeof(ans));
if(dfs(0, get_first(a, b), a, b))
{
ok = 1;
break;
}
}
cout << "Case " << ++kase << ": ";
if(ok)
{
cout << a << "/" << b << "=";
for(int i = 0; i < deep; i++)
cout << "1/" << ans[i] << "+";
cout << "1/" << ans[deep] << "\n";
}
else
cout << "No solution.\n";
}
return 0;
}
例2.Power Calculus----POJ3134
给定一个正整数n,求经过多少次乘法或除法运算可以从得到?( 一开始只有 )中间结果也是可以复用的。
It is possible to compute x31 with six operations (five multiplications and one division):
样例输入
1
31
70
91
473
512
811
953
0
样例输入
0
6
8
9
11
9
13
12
思路
深搜两个方向(乘,除)+IDA*剪枝
(1)不去存储的次方,就算=2,的1000次方也是很大,利用,存储当前的指数即可。
(2).每次新计算的值必须从未出现过;用当前值和之前产生的值进行加减运算得到新的值,判断是否等于n。
(3)每次操作最快的增长方式:自己×自己。
设置vis[i]数组:操作第i次时到达了x的vis[i]次方
(4).每次新计算的值进行还可以执行的运算次数的幂运算仍然小于,即新值左移还可以执行的次数小于n则一定不成立;(左移运算 a<<b :代表)
if((vis[n_deep]<<(deep-n_deep))<n)
return; //指数相加,爆炸增长依然无法到达n,放弃
AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int vis[2050]; //vis[i]:操作第i次时到达了x的vis[i]次方
int deep,flag,n;
void dfs(int n_deep)
{
if(flag)
return;
if(n_deep>deep)
return; //当前操作次数已超过规定次数
if((vis[n_deep]<<(deep-n_deep))<n)
return; //指数相加,爆炸增长依然无法到达n,放弃
if(vis[n_deep]==n)
{//找到了
flag=1;
return;
}
for(int i=1; i<=n_deep; i++)
{
int t=vis[n_deep]+vis[i]; //乘法
if(t>0&&t<2000)
{
vis[n_deep+1]=t;
dfs(n_deep+1);
}
t=vis[n_deep]-vis[i]; //除法
if(t>0&&t<2000)
{
vis[n_deep+1]=t;
dfs(n_deep+1);
}
}
}
int main()
{
while(~scanf("%d",&n)&&n)
{
deep=0;
flag=0;
vis[1]=1;//初始是x的1次幂
while(!flag)
{
deep++;
dfs(1);
}
printf("%d\n",deep-1);
}
}
例3.DNA sequence---HDU1560
如上图所示,给出n个长度不超过8的字符串(只包含'A','G','C','T'),请你给出一个字符串s,请输出最短的长度。
样例输入
1
4
ACGT
ATGC
CGTT
CAGT
样例输出
8
思路
深搜四个方向('A','G','C','T')+IDA*剪枝
(1)每次DFS四个方向('A','G','C','T'),控制当前的深度,若递归达到指定深度后仍无法达成目标,则结束递归。
(2)s的最短长度为n个字符串中最长的字符串的长度len,所以深度从len开始
(3)设置match数组:
match[i]==x 对于当前的字符串s而言,第i个字符串中已经有x个字符匹配上了
(4)递归结束条件:
1.已出现答案
2.当前长度大于规定长度
3.当前长度+最多未匹配的字符串的未匹配字符数量>规定长度
AC代码
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
char words[8]="AGCT";
bool vis[8][8];
char mp[8][8];
int t,n,m,ans,len;
void dfs(int n_len,int match[])
{
if(ans)
return;
if(n_len>len)
return ;
int no_match=0;
for(int i=0; i<n; i++)
no_match=max((int)(strlen(mp[i]))-match[i],no_match);
if(no_match==0) //没有不匹配的字符了
{
ans=n_len;
return;
}
if(no_match+n_len>len)
return;
for(int i=0; i<4; i++)//跑四个方向('A','G','C','T')
{
bool flag=false;
int n_match[8];
for(int j=0; j<n; j++)
{
if(mp[j][match[j]]==words[i])//当前方向==第j个字符串的第match[j]位置上的字母
{
flag=true;
n_match[j]=match[j]+1;//第j个字母匹配到的字母个数加一
}
else
n_match[j]=match[j];
}
if(flag)//匹配到一个字母,继续向下走
dfs(n_len+1,n_match);
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
len=-1;
scanf("%d",&n);
int match[8]; //第i个字符串已经匹配的字符个数
for(int i=0; i<n; i++)
{
scanf("%s",mp[i]);
len=max((int)(strlen(mp[i])),len);
match[i]=0;
//printf("%s\n",mp[i]);
}
ans=0;
while(!ans)
{
dfs(0,match);
len++;
}
printf("%d\n",ans);
}
}