poj2286 the rotation game IDA*

本文介绍了一种解决特定类型问题的有效算法——迭代加深搜索(IDA*),并以POJ2286 The Rotation Game为例,详细展示了如何利用该算法进行高效搜索。文章通过具体实现代码,解释了剪枝策略的重要性及其如何帮助提高搜索效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自https://blog.youkuaiyun.com/urecvbnkuhbh_54245df/article/details/5856756 做了点补充

迭代加深搜索通常用于求最小次数的情况下

/* 
 *  POJ2286 The Rotation Game 
 *  解题思路:使用迭代加深的深度搜索算法,这里非常要注意还是剪枝的问题 
 *  只有较好的针对题目环境的剪枝,才能提高搜索效率  
*/   
#include<iostream>  
#include<fstream>  
#include<string>  
using namespace std;  
  
//使用数组表示游戏局面   
int map[25],countArray[25];  
//搜索深度 , 最终的局面中心数字   
int DEPTH,res;  
  
char outPath[100];  
  
//判断是否到达了目标局面   
bool isOk(const int* state){  
      
    int tmp = state[7];  
    if( tmp!= state[8] || tmp!=state[9] || tmp!= state[12]   
        || tmp!= state[13] || tmp!=state[16] || tmp!= state[17]   
        || tmp!= state[18] ){  
              
            return false  ;   
        }  
    return true ;  
}  
  
//统计局面的中心区域含有相同数字的最大数量   
int countMaxSameNumber(const int* state){  
      
    memset(countArray , 0 ,sizeof(countArray) ) ;         
    countArray[ state[7] ]++; countArray[state[8]] ++; countArray[state[9]]++;   
    countArray[ state[12] ]++; countArray[ state[13] ]++ ; countArray[state[16]]++;  
    countArray[state[17]] ++ ; countArray[state[18]]++;  
      ////因为 只有数字1 2 3 只需比较countArry[2] countArry[1] countArry[3] 
    countArray[2] = (countArray[2]>countArray[1]) ? countArray[2]: countArray[1];  
    return max(countArray[2],countArray[3]);  
}  
  
void changeState(int *state,int a1,int a2,int a3,int a4,int a5,int a6,int a7){  
      
    int tmp = state[a1];  
    state[a1]=state[a2],state[a2]=state[a3],state[a3]=state[a4],  
    state[a4]=state[a5],state[a5]=state[a6],state[a6]=state[a7],state[a7]=tmp;  
      
}  
  
//迭代加深搜索  
//state:当前局面 currDepth :当前所处的搜索深度 preDir:当前搜索选择的旋转的方向   
bool dfsSearch( int* state ,int currDepth , int preDir) {  
      
    //剪枝 1  : 本质上使用的就是IDA*估价函数进行剪枝
    //如果当前可以递归的深度已经小于局面中心数字所需量 ,那么·即使是最理想的递归(即每次递归都能使局面中心数字所需量减少一个),也不可能将8个数字都全部化为一样的数字
    //DEPTH 其实就是阈值 DEPTH++ 其实就是增大阈值扩大搜索范围 通过阈值的设定,所得解必定是要求的最小解
    if( DEPTH - currDepth < 8- countMaxSameNumber(state))  
        return false ;  
      
    //超过了当前的搜索深度   
    if( DEPTH <= currDepth )  
        return false ;  
          
    int tmp[25];  
    for(int i=1 ; i<=8 ; i++){  
          
        //剪枝2 :前后连续的相反方向的两次旋转是没有意义的   
        if( (1 == i && 6 == preDir) || (6==i && 1== preDir) )   continue ;  
        if( (2 == i && 5 == preDir) || (5==i && 2== preDir) )   continue ;  
        if( (3 == i && 8 == preDir) || (8==i && 3== preDir) )   continue ;  
        if( (4 == i && 7 == preDir) || (7==i && 4== preDir) )   continue ;  
          
    //  memcpy( tmp , state , sizeof(state)) ;  
        for(int k=1 ; k<=24 ; k++)  
            tmp[k]=state[k];  
          
        switch(i){  
            //记录搜索路径   
            case 1 : outPath[currDepth] = 'A' ; changeState(tmp,1,3,7,12,16,21,23); break;  
            case 2 : outPath[currDepth] = 'B' ; changeState(tmp,2,4,9,13,18,22,24); break;  
            case 3 : outPath[currDepth] = 'C' ; changeState(tmp,11,10,9,8,7,6,5); break;  
            case 4 : outPath[currDepth] = 'D' ; changeState(tmp,20,19,18,17,16,15,14); break;  
            case 5 : outPath[currDepth] = 'E' ; changeState(tmp,24,22,18,13,9,4,2); break;  
            case 6 : outPath[currDepth] = 'F' ; changeState(tmp,23,21,16,12,7,3,1); break;  
            case 7 : outPath[currDepth] = 'G' ; changeState(tmp,14,15,16,17,18,19,20); break;  
            case 8 : outPath[currDepth] = 'H' ; changeState(tmp,5,6,7,8,9,10,11); break;  
            default : cout<<"ERROR!"<<endl;  
        }  
          
        if( isOk(tmp) ){  
              
            res = tmp[7];  
            outPath[currDepth +1 ]='/0';  
            return true ;  
        }  
              
        if( dfsSearch(tmp , currDepth+1 , i))  
            return true ;  
    }  
      
    return false ;  
}  
  
  
int main(){  
      
    ifstream in("test.txt");  
      
    while(1){  
          
        in>>map[1];  
        if( 0 == map[1])  
            break;  
          
        for(int i=2 ; i<=24 ; i++)  
            in>>map[i];  
              
        if( isOk(map)){  
            cout<<"No moves needed"<<endl;  
            cout<<map[7]<<endl;  
        }  
          
        else{  
            DEPTH =1 ;  
            while(1){  
                if(dfsSearch(map , 0 , -1 ))  
                    break;  
                DEPTH ++ ;  
            }  
              
            cout<<outPath<<endl;  
            cout<<res<<endl;  
        }  
          
    }  
}  

如何解这道题

看到这道题无从下手

首先考虑模拟

怎么模拟

先读入数据吧

直接整条读入,看中心区域是位于哪几个地方 7 8 9 12 13 ...... 第一条链又是什么哪几个....

1 1 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3

一步步来,模拟链子的转动

其中的a1,a2....a7 这要根据是哪一条链子传入不同的数据

void changeState(int *state,int a1,int a2,int a3,int a4,int a5,int a6,int a7){  
      
    int tmp = state[a1];  
    state[a1]=state[a2],state[a2]=state[a3],state[a3]=state[a4],  
    state[a4]=state[a5],state[a5]=state[a6],state[a6]=state[a7],state[a7]=tmp;  
      
}  
  

既然已经模拟了链子的转动,就想到用dfs搜索每一次不同的转动

最终判定是不是最终结果是不是符合题目要求

//判断是否到达了目标局面   
bool isOk(const int* state){  
      
    int tmp = state[7];  
    if( tmp!= state[8] || tmp!=state[9] || tmp!= state[12]   
        || tmp!= state[13] || tmp!=state[16] || tmp!= state[17]   
        || tmp!= state[18] ){  
              
            return false  ;   
        }  
    return true ;  
}  

但这里为什么涉及IDA*

为了效率和空间

通过设定阈值,来确保得到的结果是最小的,并且减少不必要的搜索 ,其实就是剪枝

这里主要多了以下几个步骤是

step1.

统计中心区域含有相同数字的最大值 

step2. 

通过

 if( DEPTH - currDepth < 8- countMaxSameNumber(state))  
        return false ;  

剪枝

step3

每次dfs DEPTH++ 增加阈值

注意:这里没有回溯

转载于:https://www.cnblogs.com/LandingGuy/p/9280237.html

这是一道比较经典的计数问题。题目描述如下: 给定一个 $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、付费专栏及课程。

余额充值