POJ-1077 Eight (三种解法:IDA*、A*、bfs()+康托展开)

题目链接: https://cn.vjudge.net/problem/POJ-1077


(根据我自己的理解写的,有错误的地方希望大家可以指正一下,非常感谢!)


IDA*解法:


什么是IDA*算法?

    IDA*是基于深搜的基础上的一种优化算法。

    对于这个题来说,由于深搜算出最佳方案需要耗费很多的时间,所以我们就从假定最优解开始算起,若搜索不出来这个结果,就将假定最优解加一再搜一次,直到搜到与当前假定最优解相同的结果。搜索过程中要加上很重要的剪枝,即IDA*的核心:当前的曼哈顿距离+已走过的深度<=当前假定最优解。


什么是曼哈顿距离呢?

    曼哈顿距离其实就是初始状态到目标状态的最短距离。举个例子,一个2*2的矩阵:3 1  2 4,要想将其变为1 2  3 4,它的曼哈顿距离应该是将初始状态中每个位置上的数到它的目标状态的位置的最短距离之和,3的位置是(0,0),而它的目标状态位置为(1,0),所以3到目标状态位置的最短距离应该是1,以此类推可算出曼哈顿距离。为了便于计算,在执行代码时,将'  x ' 变为‘ 0 ,此题在求曼哈顿距离时要注意忽略 0 。


什么是当前假定最优解?

    其实就是初始的曼哈顿距离。每搜索一次时,这个值会加一。


如何判断八数码问题可解?

    在这里不详细解释了,大致说一下。利用逆序数的奇偶性来判断,按行的顺序将矩阵接起来,变为一行数,计算逆序数,忽略 0。若为偶数则可解,否则不可解(必要不充分条件);网上很多博客解释,在这里推荐一个: https://blog.csdn.net/u010398265/article/details/50987577

IDA*代码:

#include<stdio.h>
#include<string.h>
#include<math.h>
int Map[4][4];
int max1,min1;
int start_x,start_y;
int flag;
int c[4][2]= {-1,0,0,1,0,-1,1,0};
int path[10000];
int goal[10],start[10];

int HMD()//曼哈顿距离;
{
    int i,j,num=0;
    for(i=0; i<3; i++)
        for(j=0; j<3; j++)
            if(Map[i][j]==0)
                continue;
            else
                num+=fabs((Map[i][j]-1)/3-i)+fabs((Map[i][j]-1)%3-j);
    return num;
}

void start_xyz()//0的初始位置;
{
    int i,j;
    for(i=0; i<3; i++)
        for(j=0; j<3; j++)
            if(Map[i][j]==0)
            {
                start_x=i;
                start_y=j;
                return ;
            }
}

void swap(int *a,int *b)//交换函数;
{
    int t;
    t=*a;
    *a=*b;
    *b=t;
}

void dfs(int x,int y,int s,int last_step)//搜索过程;
{
    if(flag)
        return ;
    if(s==max1)//与当前假定最优解相同;
    {
        int l=HMD();
        if(l==0)//与目标状态相同;
        {
            min1=s;
            flag=1;
        }
        return ;
    }
    for(int i=0; i<4; i++)
    {
        if(last_step+i==3&&s>0)
            continue;
        int dx=x+c[i][0];
        int dy=y+c[i][1];
        if(dx<0||dy<0||dx>=3||dy>=3)
            continue;
        swap(&Map[dx][dy],&Map[x][y]);//先交换位置,再判断是否符合条件;
        if(HMD()+s<=max1&&!flag)//IDA*;
        {
            path[s]=i;
            dfs(dx,dy,s+1,i);
            if(flag)
                return ;
        }
        swap(&Map[dx][dy],&Map[x][y]);//回溯;
    }
}

int check()//判断是否有解;
{
    int i,j,num1=0,num2;
    for(i=0; i<8; i++)
        for(j=i+1; j<8; j++)
        {
            if(start[j]<start[i])
                num1++;
        }
    if(num1%2)
        return 0;
    else
        return 1;
}

int main()
{
    char a[110];
    char dis[4]= {'u','r','l','d'};
    int i,j;
    gets(a);
    int k=0;
    int sum=-1;
    for(i=0; i<strlen(a); i++)
    {
        if(a[i]==' ')
            continue;
        sum++;
        if(a[i]=='x')
            Map[sum/3][sum%3]=0;
        if(a[i]>='1'&&a[i]<='8')
        {
            Map[sum/3][sum%3]=a[i]-'0';
            start[k++]=a[i]-'0';
        }
    }
    start_xyz();
    int ans=check();//判断是否有解;
    if(!ans)
    {
        printf("unsolvable\n");
        return 0;
    }
    max1=HMD();//初始假定最优解;
    if(max1==0)
    {
        printf("\n");
        return 0;
    }
    flag=0;
    while(!flag)
    {
        dfs(start_x,start_y,0,0);
        if(!flag)
            max1++;
    }
    for(i=0; i<min1; i++)//输出路径方向;
        printf("%c",dis[path[i]]);
    printf("\n");
    return 0;
}



A*解法:

     A*是基于广搜的基础上的优化算法。

    启发式搜索。也需要计算曼哈顿距离。用一个优先队列,以当前深度+当前曼哈顿距离的值小的优先;用逆序数判断是否有解。

A*代码:

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
#include<math.h>
using namespace std;
int c[4][2]= {0,1,1,0,-1,0,0,-1};
char D[4]= {'r','d','u','l'};
int book[3700000];
int C[9]= {1,1,2,6,24,120,720,5040,40320};

struct node
{
    int pos;
    int step;
    int state[10];
    int HMD;
    int cantor;
    bool operator < (const node h)const
    {
        return step+HMD>h.step+h.HMD;
    }
};

struct yun
{
    int Node;
    int dis;
} path[3700000];

int is_solve(int a[])
{
    int i,j,ans=0;
    for(i=0; i<8; i++)
        for(j=i+1; j<9; j++)
        {
            if(a[i]==9||a[j]==9)continue;
            if(a[j]<a[i])
                ans++;
        }

    if(ans&1)
        return 1;
    else
        return 0;
}

int hmd(int ma[])
{
    int ans=0,i;
    for(i=0; i<9; i++)
    {
        if(ma[i]==9)
            continue;
        ans+=fabs((ma[i]-1)/3-i/3)+fabs((ma[i]-1)%3-i%3);
    }
    return ans;
}

int Cantor(int a[])
{
    int i,j,l,ans=0;
    for(i=0; i<8; i++)
    {
        l=0;
        for(j=i+1; j<9; j++)
            if(a[j]<a[i])
                l++;
        ans+=C[8-i]*l;
    }
    return ans;
}

void A_star(int pos,int a[])
{
    int i;
    priority_queue<node> Q;
    node st,en;
    for(i=0; i<9; i++)
        st.state[i]=a[i];
    st.pos=pos;
    st.HMD=hmd(st.state);
    st.cantor=Cantor(st.state);
    st.step=0;
    Q.push(st);
    while(Q.size())
    {
        st=Q.top();
        Q.pop();//printf("Cantor: %d  Step: %d  Pos: %d\n",st.cantor,st.step,st.pos);
        if(st.cantor==0)
        {
            int k=0;
            int p[10000];
            for(i=1; i<=st.step; i++)
            {
                p[i]=path[k].dis;
                k=path[k].Node;
            }
            for(i=st.step;i>=1;i--)
                printf("%c",D[p[i]]);
            printf("\n");
            return ;
        }
        int x=st.pos/3;
        int y=st.pos%3;
        for(i=0; i<4; i++)
        {
            int dx=x+c[i][0];
            int dy=y+c[i][1];
            int dz=dx*3+dy;
            if(dx<0||dy<0||dx>2||dy>2)
                continue;

            for(int j=0; j<9; j++)
                en.state[j]=st.state[j];
            swap(en.state[dz],en.state[st.pos]);
         //   printf("%d \n",dz);
            int kk=Cantor(en.state);
         //   printf("KK %d KK\n",kk);
            if(book[kk])
                continue;
         //   printf("%d****\n",i);
            book[kk]=1;
            en.cantor=kk;
            en.pos=dz;

            en.step=st.step+1;
         //   printf("%d**>>\n");
            en.HMD=hmd(en.state);
            path[kk].dis=i;
            path[kk].Node=st.cantor;

            Q.push(en);
        }
    }
}

int main()
{
    char str[100];
    gets(str);
    int puzzle[10],pos;
    int k=0,i;
    for(i=0; i<strlen(str); i++)
    {
        if(str[i]!=' ')
        {
            if(str[i]=='x')
            {
                puzzle[k++]=9;
                pos=k-1;
            }
            else
                puzzle[k++]=str[i]-'0';
        }
    }
    int is=is_solve(puzzle);
    if(is)
        printf("unsolvable\n");
    else
        A_star(pos,puzzle);
}

康托展开+bfs():

    康拓展开判重+bfs();

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<iostream>
using namespace std;
int flag;
int b[10];
int h[10]={1,1,2,6,24,120,720,5040,40320,362880};
int book[370000];
int path[370000],dis[370000],ans[370000];
char d[4]={'u','r','d','l'};
int c[4][2]={-1,0,0,1,1,0,0,-1};

struct yun
{
    int num[10];
    int s;
    int pos;
};

int cantor(int aa[])
{
    int i,j,l,ans=0;
    for(i=0;i<8;i++)
    {
        l=0;
        for(j=i+1;j<9;j++)
        {
            if(aa[j]<aa[i])l++;
        }
        ans+=h[8-i]*l;
    }
    return ans;
}

void bfs(int k)
{
    int i;
    queue<yun> Q;
    yun st,en;
    st.pos=k;
    for(i=0;i<9;i++)
        st.num[i]=b[i];
    st.s=0;
    Q.push(st);
    while(Q.size())
    {
        st=Q.front();
        Q.pop();
        if(cantor(st.num)==0)
        {
            int start=0;
            for(i=st.s;i>0;i--)
            {
                ans[i]=dis[start];
                start=path[start];
            }
            for(i=1;i<=st.s;i++)
                printf("%c",d[ans[i]]);
            printf("\n");
            flag=1;
            return ;
        }
        int x=st.pos/3,y=st.pos%3;
        for(i=0;i<4;i++)
        {
            int dx=x+c[i][0];
            int dy=y+c[i][1];
            int dz=dx*3+dy;
            if(dx<0||dy<0||dx>=3||dy>=3)continue;
            for(int j=0;j<9;j++)
                en.num[j]=st.num[j];
            swap(en.num[dz],en.num[st.pos]);
            int kk=cantor(en.num);
            if(book[kk])continue;
            en.s=st.s+1;
            en.pos=dz;
            book[kk]=1;
            path[kk]=cantor(st.num);
            dis[kk]=i;
            Q.push(en);
        }
    }
}

int main()
{
    int i,e;
    char A[100];
    gets(A);
    int k=0;
    for(i=0;i<strlen(A);i++)
    {
        if(A[i]!=' ')
        {

            if(A[i]=='x')
            {
                b[k++]=9;
                e=k-1;
            }
            else
            {
                b[k++]=A[i]-'0';
            }
        }
    }
    flag=0;
    memset(book,0,sizeof(book));
    book[cantor(b)]=1;
    bfs(e);
    if(!flag)
        printf("unsolvable\n");
}






  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值