UVA11212 Editing a Book (IDA*)

对于可以用回溯法求解但解答树的深度没有明显上限的题目,可以考虑用迭代加深搜索(iterative deepening)。如果设计出以一个乐观估价函数,预测从当前结点至少还要扩展基层结点才能得到解,则迭代加神搜索编程了IDA*算法。——《算法竞赛入门经典》p207

讲真看书里第一个埃及分数问题,看得我是一脸懵逼。感觉好像是这么个道理,但是没懂到底什么意思。接着就跳过先看了下面那道例题,也就是Editing a Book,看完感觉这题更容易理解点,我好像也能写出来的样子,就试着写了写,两个多小时终于A了大哭大哭。这过程中也确实开始对这个算法有了些理解。

前几天碰巧因为上一篇博客那道题的原因,去看了关于A*算法,所以我在看埃及分数问题的时候,书中提到“当前结点n的深度g(n),乐观估价函数为h(n),则当g(n)+h(n)>maxd的时候应该剪枝”,当时我第一反应:“呀,这玩意儿怎么跟A*这么像”。所以当我看到这个算法名字的时候,就明白了,很直观,就是迭代加深搜加A*。所以,回顾先下A*。

简单来说,A*就是给一般的广搜加一个判断条件,让程序知道,沿这条路下去,有没有机会到达终点,或者说是不是一条不过分绕远的路。我觉得我看的那篇博客里讲得很形象顺便给下传送门吧(A*算法),就相当于走到迷宫这一格的时候,站起来看看终点是不是在我们现在正走向的那个方向,如果是就继续下去,不是就停止扩展这一层。

而IDA*就是我们设计出一个估价函数,来判断,在这一次的迭代中,我们有没有机会通过深搜到达终点的状态。而每一次迭代也是从初始状态开始重新深搜(这也是我一开始搞不懂的地方)。

直接用题目来说:

先设 后继不正确的数字个数为h,当前搜索深度为d,当前迭代深度限制是maxd

通过分析,我们可以知道,每一次剪切时h最多减少3,那么我们就可以得出这样的结论,(maxd-d)必须>=(h/3),

前半部分的含义是,还能剪切几次(即搜索几次),后半部分则是假设每次剪切都能影响最多数字的后继,那么至少需要h/3次剪切才能完成。那么这个式子的意思就是

如果剩余剪切次数少于h/3,那么肯定没有机会了,因此就可以剪枝。

以上是最核心的思想。

剩下还要提的就是,maxd(即迭代次数)最好能给出个上限,本题就可以给出上限8(其实本题上限是5,别问我怎么证明,用程序跑出来的。。。)


总结下这种题目的重点:1.每一次迭代都从初始状态开始深搜 2.设计估价函数来剪枝


代码如下

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int MAX=11;
int N;
int maxd;
int kase=0;
struct Node{
    int arr[MAX];
};
int h(int*a)
{
    int cnt = 0;
    for (int i = 0; i < N;i++)
        if (a[i] + 1 != a[i + 1])
            cnt++;
    return cnt;
}
void shift(Node& tmp,Node& node,int i,int j,int k){
    if(i>k){
    for(int p=1;p<=j-i+1;p++)
        tmp.arr[k+p]=node.arr[i+p-1];
    for(int p=1;p<=i-k-1;p++)
        tmp.arr[k+j-i+1+p]=node.arr[k+p];
    }
    else{
        for(int p=1;p<=k-j;p++)
            tmp.arr[i+p-1]=node.arr[j+p];
        for(int p=0;p<j-i+1;p++)
            tmp.arr[k-p]=node.arr[j-p];
    }
}
bool dfs(int d,Node node){

    if(3*(maxd-d)<h(node.arr)) return false;
    if(h(node.arr)==0) return true;
    int i,j,k;
    for(i=0;i<N;i++)
        for(j=i;j<N;j++)
            for(k=0;k<N;k++){
                if(k>=i&&k<=j) continue;
                Node tmp;
                memcpy(tmp.arr,node.arr,sizeof(node.arr));
                shift(tmp,node,i,j,k);
                if(dfs(d+1,tmp)) return true;
            }
    return false;
}
int solve(Node node){
    for(maxd=1;maxd<8;maxd++){
       if(dfs(0,node)) break;
    }
    return maxd;
}
int main()
{
    while(scanf("%d",&N)&&N){
    int res;
    Node node;
    for(int i=0;i<N;i++)
        scanf("%d",&node.arr[i]);
        node.arr[N]=N+1;
    if(h(node.arr))
        res=solve(node);
    else
        res=0;
    printf("Case %d: %d\n",++kase,res);
    }
    return 0;
}

ps:初始化数组的时候,一定要记得最后一个数字后面要扩展一位,这样在判断后继正确与否的时候就不会出问题。。。哎,我就是一开始没注意,卡了老半天。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值