迭代加深搜索-IDA*算法-两个紫书例题-我的理解*

(有任何问题欢迎留言或私聊

题目一:埃及分数

题目链接及相关信息见于:Lrj的《算法竞赛入门经典》第二版P206

题意:

在古埃及,人们使用单位分数的和(即1/a,a是自然数)表示一切有理
数。 例如,2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为在加数中不允许有相同的。
对于一个分数a/b,表示方法有很多种,其中加数少的比加数多的好,如果加数个数相
同,则最小的分数越大越好。 例如,19/45=1/5+1/6+1/18是最优方案。
输入:输入a,b,m。a,b意思同上,m表示有m个数不能做分母,接下来输入这m个数字。

思路:

套Lrj紫书上的模板,从小到大枚举深度上限maxd,dfs搜索求解
减枝:
1.每次枚举不是从1开始枚举,而是从最小的一个数x且满足1/x < a/b,从x开始枚举
2.得到上面的x后,定义乐观估价函数f^(n)=g^(n)+h^(n)。
g^(n)是当前深度,h^(n)是到目标需要的步数,这题的处理没有那么麻烦。
减枝就是h^(n)=maxd-g^(n)+1,如果h^(n)/x<=a/b时,break;

AC代码:
//2410ms
#include<iostream>  
#include<algorithm>  
#include<sstream>  
#include<set>  
#include<vector>  
#include<stack>  
#include<map>  
#include<queue>  
#include<cstdlib>  
#include<cstdio>  
#include<cstring>  
#include<cmath>  
using namespace std;  
typedef long long LL;  
const int maxn = 100000+7;  
int maxd;  
LL a, b;  
LL ans[maxn], v[maxn];  
LL br[maxn],tot;
inline LL gcd(LL a,LL b){
    return b==0?a:gcd(b,a%b);
}
LL get_first(LL a,LL b){//a/b<1/c->a*c<b
    LL c=1;
    while(a*c<b){
        c++;
    }
    return c;
}
bool better(int d){
    for(int i=d;i>=0;--i){
        if(v[i]!=ans[i]){//分解地更长或最大数字更小则return true
            return ans[i]==-1||v[i]<ans[i];
        }
    }
    return false;
}
bool dfs(int d,LL from,LL aa,LL bb){
    if(d==maxd){
        for(int i=0;i<tot;++i)if(br[i]==bb/aa)return false;
        if(bb%aa)return false;//不能整除
        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));//获取那个x
    for(LL i=from;;i++){//rd/i<=aa/bb
        if((maxd-d+1)*bb<=aa*i)break;//减枝
        int flag=0;
        for(int ii=0;ii<tot;++ii){//不能含有被禁止使用地分母
            if(br[ii]==i){
                flag=1;
                break;
            }
        }
        if(flag)continue;
        v[d]=i;//aa/bb-1/i
        LL ta=aa*i-bb;
        LL tb=bb*i;
        LL tc=gcd(ta,tb);
        ta/=tc;tb/=tc;
        if(dfs(d+1,i+1,ta,tb))ok=true;
    }
    return ok;
}
int main(int argc, char const *argv[])
{
    int tim=0,t;
    scanf("%d",&t);
    while(t--){
        int m;
        ban.clear();
        tot=0;
        scanf("%lld%lld%d",&a,&b,&m);
        for(int i=0;i<m;++i){
            LL x;
            scanf("%lld",&x);
            br[tot++]=x;
        }
        for(maxd=1;;maxd++){
            memset(ans,-1,sizeof(ans));
            if(dfs(0,get_first(a,b),a,b)){
                break;
            }
        }
        printf("Case %d: %lld/%lld=",++tim,a,b );
        for(int i=0;i<=maxd;++i){
            if(i!=maxd) printf("1/%lld+",ans[i] );
            else printf("1/%lld\n",ans[i] );
        }
    }
    return 0;
}

第二题:The Rotation Game

紫书例题7-12:Uva1343,见于紫书P210

题意:

给你一个如图所示地棋盘,上面有24个格子,每个格子上的数字为1,2或3。你可以向8个方向旋转棋盘,一次移动一格,8个方向为A-H编号。求最小步骤是的中间8个数字相同,若有多个答案输出字典序最小地答案。
这里写图片描述

思路:

  1. 给这24个格子编号0-23,顺序时从左上到右下。如此可以得到中间8个格子地编号为:6,7,8,11,12,15,16,17。
  2. 给A-H8个操作编号为0-7,因为A和F,B和E,C和H,D和G互为逆操作,再用一个数组记录相应操作地逆操作编号,方便回溯地操作。
  3. 再将每个操作影响地位置坐标预处理出来。

这里写图片描述

IDA*:

还是枚举深度上限maxd,乐观估价函数f^(n)=g^(n)+h^(n)。当前搜索到的深度为g^(n),到目标状态还需要地最小步数为h^(n)。
减枝:如果g^(n)>maxd||g^(n)+h^(n)>maxd,则return;

为了求出字典序最小答案,8个操作从小到大枚举即可。

最后一个问题是怎么求出乐观估价函数中的h^(n):
我们知道中间8个数字是1,2或3。
h^(n)是到目标状态需要地最小步数,那不就很简单了吗?
目标状态是8个数字都相同,你求出同一数字出现最多的次数cnt,h^(n)=8-cnt;
具体看代码中的实现。

AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1005;
const int center[10]={6,7,8,11,12,15,16,17};//中间8个点的位置
const int reverseOP[10]={5,4,7,6,1,0,3,2};//每种操作的逆操作
const int opt[8][7]={//A-H8种操作影响的位置坐标
    {0,2,6,11,15,20,22},//A
    {1,3,8,12,17,21,23},//B
    {10,9,8,7,6,5,4},//C
    {19,18,17,16,15,14,13},//D
    {23,21,17,12,8,3,1},//E
    {22,20,15,11,6,2,0},//F
    {13,14,15,16,17,18,19},//G
    {4,5,6,7,8,9,10},//H
};
int mp[25];//地图,每个位置的数字
char ans[N];//操作数答案
bool flag;//是否搜索到
int maxd;//深度上限
inline int getNum(){
    int cnt=0;
    int num[4]={0};
    for(int i=0;i<8;++i){
        cnt=max(cnt,++num[mp[center[i]]]);
    }
    return 8-cnt;
}
void exe(int op){//移动操作
    int tmp=mp[opt[op][0]];
    for(int i=0;i<6;++i){
        mp[opt[op][i]]=mp[opt[op][i+1]];
    }
    mp[opt[op][6]]=tmp;
}
void dfs(int dep,int fa){
    if(flag)return;
    if(dep>maxd||dep+getNum()>maxd)return;//减枝
    if(getNum()==0){
        ans[dep]='\0';
        printf("%s\n",ans );
        printf("%d\n",mp[center[0]]);
        flag=true;
        return;
    }
    for(int i=0;i<8;++i){
        if(fa!=-1&&i==reverseOP[fa])continue;
        exe(i);
        ans[dep]=i+'A';
        dfs(dep+1,i);
        if(flag)return;
        exe(reverseOP[i]);//回溯过程,使用recerseOP数组比较方便
    }
}
int main(){
    while(~scanf("%d",&mp[0])&&mp[0]){
        for(int i=1;i<24;++i){
            scanf("%d",&mp[i]);
        }
        if(getNum()==0){//如果一开始8个数字就相同:
            printf("No moves needed\n%d\n",mp[center[0]]);
            continue;
        }
        flag=false;
        maxd=1;
        while(1){//迭代加深搜索:
            dfs(0,-1);
            if(flag)break;
            maxd++;
        }
    }
    return 0;
}
搜索分为盲目式搜索和启发式搜索:

dfs和bfs是盲目式搜索,而A*和IDA*式启发式搜索。
原因式A*和IDA*式有目的的朝着目标状态搜索,排序了很多没必要的搜索路径。
其精髓就是乐观估价函数f^(n)=g^(n)+h^(n):

g^(n)是搜索到当前状态花费的代价
h^(n)是从当前状态到目标状态需要花费的最小代价。
h(n)是从当前状态到目标状态花费的实际代价。(当然这个不号求出)
显然h^(n)<=h(n)必须成立。

如果要两个状态h1(n)和h2(n)均小于h(n)且h1(n) < h2(n),那么我们要选取的是最接近h(n)的那种情况,也就是h2.(实话,我也不知道为什么,今天才学的IDA*,A*还不会)

听说实现A*算法要用堆,open和close列表,启发式,pre数组记录路径,BFS扩展方式。而且g^(n)跟Dijkstra的松弛一样,需要更新。骚操作太多,学不来。。。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值