八数码问题多种解法比较(poj1077宽搜,双向宽搜,A*,IDA*+扩展)(持续更新)

有个小的优化,逆序数(除去x)的奇偶性相同,那就一定可以达到,不同一定不可以达到。
推荐: 八数码的八种境界—写的不错
需要指出上文中说(境界2)单向宽搜+哈希会超时,但事实证明能ac过。(4608KB,688ms)
(hdu1043上是同时多组输入,POJ是单组输入。
两个限时不同。HDU 上反向搜索,把所有情况打表出来。
POJ上正向搜索。)
hash需要用到康托展开(—-维基百科)

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=400000;
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};//康托展开
//         0!1!2!3! 4! 5! 6!  7!    8!      9!
bool vs[MAXN];
typedef struct _Node
{
    int s[9];
    int loc;//0的位置,把x当成0
    int sts;//康托展开的hash值
    string path;//路径
}Node;
string path;
int aim=46234;//12345780对应的hash值
int move[4][2]={{-1,0},{1,0},{0,-1},{0,1}};//u,d,l,r
char indexs[5]="udlr";
Node ncur;
int cantor(int s[])
{
    int sum=0;
    for(int i=0;i<9;i++)
    {
        int tmp=0;
        for(int j=i+1;j<9;j++)
            if(s[j]<s[i]) tmp++;
        sum+=(tmp*fac[9-i-1]);
    }
    return sum+1;
}
bool BFS(void)
{
    queue<Node> q;
    while(!q.empty()) q.pop();
    ms(vs);
    Node cur,next;
    q.push(ncur);
    while(!q.empty()){
        cur=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int tx=cur.loc/3+move[i][0],
            ty=cur.loc%3+move[i][1];
            if(tx<0||tx>2||ty<0||ty>2) continue;
            next=cur;
            next.loc=tx*3+ty;
            next.s[cur.loc]=cur.s[next.loc];
            next.s[next.loc]=0;
            next.sts=cantor(next.s);
            if(!vs[next.sts]){
                vs[next.sts]=true;
                next.path+=indexs[i];
                if(next.sts==aim) {
                    path=next.path;
                    return true;
                }
                q.push(next);
            }
        }
    }
    return false;
}
int main(int argc, char const *argv[])
{
    char ch;
    for(int i=0;i<9;i++)
    {
        scanf(" %c",&ch);
        if(ch=='x'){
            ncur.s[i]=0;
            ncur.loc=i;
        }
        else ncur.s[i]=ch-'0';  
    }
    ncur.sts=cantor(ncur.s);
    if(ncur.sts==aim) putchar('\n');
    else if(BFS()){
        cout<<path<<endl;
    }
        else puts("unsolvable");
    return 0;
}
/*
 1 2 3 x 4 6 7 5 8
*/

我的双向宽搜超时了,依然在debug中,补。。。。

A*算法
f (n)=g(n) +h(n) ,且满足以下限制
g(n)是从s0到n的真实步数(未必是最优的),因此:
g(n)>0 且g(n)>=g*(n)
h(n)是从n到目标的估计步数。估计总是过于乐观的,即 h(n)<= h*(n)
且h(n)相容,则A算法转变为A* 算法。A*正确性证明略。

h(n)的相容:
如果h函数对任意状态s1和s2还满足:
h(s1) <= h(s2) + c(s1,s2)

启发式h函数的距离与所允许的移动方式相匹配:
在正方形网格中,允许向4邻域的移动,使用曼哈顿距离(L1)。
在正方形网格中,允许向8邻域的移动,使用对角线距离(L∞)。
在正方形网格中,允许任何方向的移动,欧几里得距离(L2)可能适合,但也可能不适合。如果用A* 在网格上寻找路径,但你又不允许在网格上移动,你可能要考虑用其它形式表现该地图。
在六边形网格中,允许6个方向的移动,使用适合于六边形网格的曼哈顿距离。

在这道题中启发式函数有两种选择,一种是不在应该在的位置上数的个数,一种是不在应该在的位置的数字到应该在的位置的哈弗曼距离和,(均不计x)
显然后者优于前者。
A*的还没写。。。。。。

IDA*
即迭代加深搜索与A*结合,但其实写起来比A *好写。迭代加深搜索核心思想就是用了一个变量mLimit限制了搜索的深度,若成功则肯定是最优的,若不成功,则增加mLimit直到有解。

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <queue>
#define mabs(X) ((X)>0?(X):(-(X)))
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
bool Inv(const int *s)
{
    int iv=0;
    for(int i=0;i<9;i++)
        {
            if(s[i]==9) continue;
            for(int k=0;k<i;k++)
            {
                if(s[k]==9) continue;
                if(s[k]>s[i]) iv++;
            }
        }
    return iv%2==0;
}
bool Isans(const int *s)
{
    for(int i=0;i<8;i++)
        if(s[i]+1!=s[i+1]) return false;
    return true;
}
struct _Node
{
    int loc;
    int s[9];
};
int h(const int *s)
{
    int cost=0;
    for(int i=0;i<9;i++)
    {
        if(s[i]==9) continue;
        int x=(s[i]-1)/3,y=(s[i]-1)%3;
        cost+=mabs(i/3-x)+mabs(i%3-y);
    }
    return cost;
}
struct _Node ncur;
int pathLimit;
char path[400000];
const int move[4][2]={{-1,0},{0,-1},{0,1},{1,0}};
char indexs[5]="ulrd";
bool IDAst(struct _Node cur,int len,int pre)
{
    if(len==pathLimit){
        if(Isans(cur.s)==1){
            path[len]='\0';
            puts(path);
            return true;
        }
        else return false;
    }
    struct _Node next;
    for(int i=0;i<4;i++)
    {
        if(pre+i==3) continue;//前一步和当前所选的步反方向,重要剪枝
        int tx=cur.loc/3+move[i][0],
            ty=cur.loc%3+move[i][1];
        if(tx<0||tx>2||ty<0||ty>2) continue;
        next=cur;
        next.loc=tx*3+ty;
        next.s[cur.loc]=cur.s[next.loc];
        next.s[next.loc]=9;
        path[len]=indexs[i];
        if(len+h(next.s)<pathLimit&&IDAst(next,len+1,i))//重要剪枝
            return true;
    }
    return false;
}
int main(int argc, char const *argv[])
{
    char ch;
    for(int i=0;i<9;i++)
    {
        scanf(" %c",&ch);
        if(ch=='x') ncur.loc=i,ncur.s[i]=9;
        else ncur.s[i]=ch-'0';
    }
    if(!Inv(ncur.s)) puts("unsolvable");
    else{
        pathLimit=h(ncur.s);
        while(!IDAst(ncur,0,-1)) pathLimit++;     
    }
    return 0;
}
/*
 1 2 3 x 4 6 7 5 8
 2 3 4 1 5 x 7 6 8
*/

uva10181
题目说不超过45步,所以刚开始直接IDAstar搜索,但是在搜到30层左右的时候基本就跑不动了,所以多加了一个直接判断是否存在解的is_ok函数,居然过了,说明这道题的数据没有那种要到40步左右才能完成的。
如果空格左右移动它的逆序数是不变的,但是如果上下移动的话逆序数奇偶性改变,所以只需判断现在状态下的逆序数状态和空格所在行数的奇偶性是否相同,如果相同(目标状态的逆序数为0,空格所在行数为3,奇偶性不同)无解,不同必有解。

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <stack>
#include <vector>
#include <string.h>
#include <math.h>
#include <queue>
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
#define mabs(X) ((X)>0?(X):(-(X)))
int h(const int *s)
{
    int rt=0;
    for(int i=0;i<16;i++)
    {
        if(s[i]==0) continue;
        int x=(s[i]-1)/4,y=(s[i]-1)%4;
        rt+=mabs(x-i/4)+mabs(y-i%4);
    }
    return rt;
}
bool is_ok(const int *s)
{
    int num=0,l;
    for(int i=0;i<16;i++)
    {
        if(s[i]==0) l=i/4;
        else {
            for(int j=i+1;j<16;j++)
             if(s[j]&&s[i]>s[j])
                num++;
            }
    }
    return num%2!=l%2;
}
struct _Node
 {
    int num[17];
    int pos;
 }tcur;
int maxdep; 
char path[55];
const int movest[5][2]={{-1,0},{0,-1},{0,1},{1,0}};
const char Indexs[6]="ULRD";
bool IDAS(struct _Node cur,int d,int pre)
{
    if(d==maxdep){
        if(h(cur.num)==0) {
            path[d]='\0';
            puts(path);
            return true;
        }
        else return false;
    }
    struct _Node tmp;
    for(int i=0;i<4;i++)
    {
        if(i+pre==3) continue;
        int tx=cur.pos/4+movest[i][0],ty=cur.pos%4+movest[i][1];
        if(tx<0||tx>3||ty<0||ty>3) continue;
        tmp=cur;
        tmp.pos=tx*4+ty;
        tmp.num[cur.pos]=cur.num[tmp.pos];
        tmp.num[tmp.pos]=0;
        path[d]=Indexs[i];
        if(d+h(tmp.num)<maxdep&&IDAS(tmp,d+1,i))
            return true;
    }
    return false;
}
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    while(n--){
        for(int i=0;i<16;i++)
        {
            scanf("%d",&tcur.num[i]);
            if(tcur.num[i]==0)
                tcur.pos=i;
        }
        if(!is_ok(tcur.num)) puts("This puzzle is not solvable.");
        else {
            maxdep=h(tcur.num);
            while(!IDAS(tcur,0,-1)) maxdep++; 
        }
    }
    return 0;
}
/*
2
2 3 4 0
1 5 7 8
9 6 10 12
13 14 11 15
13 1 2 4
5 0 3 7
9 6 10 12
15 8 11 14

1
13 1 2 4
5 0 3 7
9 6 10 12
15 8 14 11

1
1 2 3 4
5 6 7 8
9 10 11 12
13 14 0 15
*/

推荐,也是自己要补的题poj3131(立体八数码),hdu3567(加强点的八数码)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一道比较经典的计数问题。题目描述如下: 给定一个 $n \times n$ 的网格图,其一些格子被标记为障碍。一个连通块是指一些被标记为障碍的格子的集合,满足这些格子在网格图连通。一个格子是连通的当且仅当它与另一个被标记为障碍的格子在网格图有公共边。 现在,你需要计算在这个网格图,有多少个不同的连通块,满足这个连通块的大小(即包含的格子数)恰好为 $k$。 这是一道比较经典的计数问题,一般可以通过计算生成函数的方法来解决。具体来说,我们可以定义一个生成函数 $F(x)$,其 $[x^k]F(x)$ 表示大小为 $k$ 的连通块的个数。那么,我们可以考虑如何计算这个生成函数。 对于一个大小为 $k$ 的连通块,我们可以考虑它的形状。具体来说,我们可以考虑以该连通块的最左边、最上边的格子为起点,从上到下、从左到右遍历该连通块,把每个格子在该连通块的相对位置记录下来。由于该连通块的大小为 $k$,因此这些相对位置一定是 $(x,y) \in [0,n-1]^2$ 的 $k$ 个不同点。 现在,我们需要考虑如何计算这些点对应的连通块是否合法。具体来说,我们可以考虑从左到右、从上到下依次处理这些点,对于每个点 $(x,y)$,我们需要考虑它是否能够与左边的点和上边的点连通。具体来说,如果 $(x-1,y)$ 和 $(x,y)$ 都在该连通块且它们在网格图有公共边,那么它们就是连通的;同样,如果 $(x,y-1)$ 和 $(x,y)$ 都在该连通块且它们在网格图有公共边,那么它们也是连通的。如果 $(x,y)$ 与左边和上边的点都不连通,那么说明这个点不属于该连通块。 考虑到每个点最多只有两个方向需要检查,因此时间复杂度为 $O(n^2 k)$。不过,我们可以使用类似于矩阵乘法的思想,将这个过程优化到 $O(k^3)$ 的时间复杂度。 具体来说,我们可以设 $f_{i,j,k}$ 表示状态 $(i,j)$ 所代表的点在连通块,且连通块的大小为 $k$ 的方案数。显然,对于一个合法的 $(i,j,k)$,我们可以考虑 $(i-1,j,k-1)$ 和 $(i,j-1,k-1)$ 这两个状态,然后把点 $(i,j)$ 加入到它们所代表的连通块。因此,我们可以设计一个 $O(k^3)$ 的 DP 状态转移,计算 $f_{i,j,k}$。 具体来说,我们可以考虑枚举连通块所包含的最右边和最下边的格子的坐标 $(x,y)$,然后计算 $f_{x,y,k}$。对于一个合法的 $(x,y,k)$,我们可以考虑将 $(x,y)$ 所代表的点加入到 $(x-1,y,k-1)$ 和 $(x,y-1,k-1)$ 所代表的连通块。不过,这里需要注意一个细节:如果 $(x-1,y)$ 和 $(x,y)$ 在网格图没有相邻边,那么它们不能算作连通的。因此,我们需要特判这个情况。 最终,$f_{n,n,k}$ 就是大小为 $k$ 的连通块的个数,时间复杂度为 $O(n^2 k + k^3)$。 参考代码:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值