DFS--IDA*

目录

迭代加深搜索

IDA*算法核心

例1.埃及分数

例2.Power Calculus----POJ3134

例3.DNA sequence---HDU1560

 

 



迭代加深搜索

迭代加深搜索(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个分数之和为\frac{c}{d},而第i个分数为\frac{1}{e},则接下来至少还需要\frac{\frac{a}{b}-\frac{c}{d}}{\frac{1}{e}}个分数,总和才能达到\frac{a}{b}

 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,求经过多少次乘法或除法运算可以从x得到x^{n}?( 一开始只有x^{1} )中间结果也是可以复用的。

It is possible to compute x31 with six operations (five multiplications and one division):

x^{2}=x*x ; x^{4} = x^{2}*x^{2} ; x^{8} = x^{4} * x^{4} ; x^{16} = x^{8} * x^{8} ; x^{32} = x^{16} * x^{16} ; x^{31} = x^{32} / x

样例输入

1
31
70
91
473
512
811
953
0

样例输入

0
6
8
9
11
9
13
12

思路

深搜两个方向(乘,除)+IDA*剪枝

(1)不去存储xn次方,就算x=2,x的1000次方也是很大,利用x^{n}*x^{m}=x^{n+m},存储当前的指数即可。

(2).每次新计算的值必须从未出现过;用当前值和之前产生的值进行加减运算得到新的值,判断是否等于n。

(3)每次操作最快的增长方式:自己×自己。

设置vis[i]数组:操作第i次时到达了x的vis[i]次方

(4).每次新计算的值进行还可以执行的运算次数的幂运算仍然小于x^{n},即新值左移还可以执行的次数小于n则一定不成立;(左移运算 a<<b :代表a*2^{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);
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值