hdu2567 EightII(IDA*/逆向BFS打表)

本文探讨了八数码游戏(又称九宫格)的IDA*算法求解策略,对比了A*算法的不足,详细介绍了如何利用IDA*算法找到从状态A到状态B的最短操作序列,确保解决方案的字典序最小。文章提供了完整的C++代码实现,包括状态表示、启发式函数设计、搜索过程及逆向BFS思路。

Eight II
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 130000/65536 K (Java/Others)
Total Submission(s): 5014 Accepted Submission(s): 1096

Problem Description
Eight-puzzle, which is also called “Nine grids”, comes from an old game.

In this game, you are given a 3 by 3 board and 8 tiles. The tiles are numbered from 1 to 8 and each covers a grid. As you see, there is a blank grid which can be represented as an ‘X’. Tiles in grids having a common edge with the blank grid can be moved into that blank grid. This operation leads to an exchange of ‘X’ with one tile.

We use the symbol ‘r’ to represent exchanging ‘X’ with the tile on its right side, and ‘l’ for the left side, ‘u’ for the one above it, ‘d’ for the one below it.

A state of the board can be represented by a string S using the rule showed below.

The problem is to operate an operation list of ‘r’, ‘u’, ‘l’, ‘d’ to turn the state of the board from state A to state B. You are required to find the result which meets the following constrains:

  1. It is of minimum length among all possible solutions.
  2. It is the lexicographically smallest one of all solutions of minimum length.

Input
The first line is T (T <= 200), which means the number of test cases of this problem.

The input of each test case consists of two lines with state A occupying the first line and state B on the second line.
It is guaranteed that there is an available solution from state A to B.

Output
For each test case two lines are expected.

The first line is in the format of “Case x: d”, in which x is the case number counted from one, d is the minimum length of operation list you need to turn A to B.
S is the operation list meeting the constraints and it should be showed on the second line.

Sample Input
2
12X453786
12345678X
564178X23
7568X4123

Sample Output
Case 1: 2
dd
Case 2: 8
urrulldr

A*不太能处理字典序的问题,靠入队顺序没办法判,比如说一个字典序不靠前的移动序列过早的少估了,那么最后结果就是错的。。。相比起来IDA*有先天优势。同时有些大常数的剪枝没什么卵用
可以不连续的加深,需要维护一个估计的最小深度。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
const LL mod=1e9+7;
const double eps=1e-9;
string Input;
char str[10];
int dx[4]={1,0,0,-1};
int dy[4]={0,-1,1,0};
char nxtdir[5]="dlru";
char predir[5]="urld";
int is[500000];
//int tt[500000];
int pos[10];
stack<char>ans;
struct node{
    char mp[10];
    int x,y,h,hsh;
}st;
int cantor(char *s){
    int ans=0;
    for(int i=0;i<8;++i){
        for(int j=i+1;j<9;++j)if(s[j]<s[i])ans++;
        ans*=(8-i);
    }
    return ans;
}
inline int trans(int x,int y){return 3*(x-1)+y-1;}
int get_h(char *s){
    int h=0;
    for(int i=0;i<9;i++){
        if(s[i]!=9)h+=(abs(i/3-pos[s[i]]/3)+abs(i%3-pos[s[i]]%3));
    }
    return h;
}
int debug(){
    for(int i=0;i<9;i++)cout<<(int)st.mp[i];
    cout<<' '<<st.h<<endl;
}
int IDA(int mxd,int d,int pre=-1){
    //debug();
    if(!st.h)return 1;
    if(d+st.h>mxd||is[st.hsh]<d)return 0;
    //if(tt[st.hsh]==mxd&&is[st.hsh]==d))return 0;
    //tt[st.hsh]=mxd;
    //is[st.hsh]=d;
    node tmp=st;
    for(int i=0;i<4;i++){
        if(i+pre==3)continue;
        st.x=tmp.x+dx[i];
        st.y=tmp.y+dy[i];
        if(st.x>3||st.x<1||st.y<1||st.y>3){st=tmp;continue;}
        swap(st.mp[trans(st.x,st.y)],st.mp[trans(tmp.x,tmp.y)]);
        //st.hsh=cantor(st.mp);
        st.h=get_h(st.mp);
        if(IDA(mxd,d+1,i)){ans.push(nxtdir[i]);return 1;}
        st=tmp;
    }
    //debug();
    return 0;
}
void store(string p,node &s){
    int k=0;
    for(int i=0;i<Input.length();i++){
        if(Input[i]=='X'){
            Input[i]='9';
            s.x=k/3+1;
            s.y=k%3+1;
        }
        if(Input[i]==' ')continue;
        s.mp[k++]=Input[i]-'0';
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin>>T;
    for(int cas=1;cas<=T;cas++){
        node ed;
        cin>>Input;
        store(Input,st);
        cin>>Input;
        store(Input,ed);
        for(int i=0;i<9;i++)pos[ed.mp[i]]=i;
        st.h=get_h(st.mp);
        st.hsh=cantor(st.mp);
        memset(is,INF,sizeof is);
        //memset(tt,-1,sizeof tt);
        for(int i=st.h;!IDA(i,0);i++){}
        cout<<"Case "<<cas<<": "<<ans.size()<<endl;
        while(!ans.empty()){cout<<ans.top();ans.pop();}
        cout<<endl;
    }
    return 0;
}

逆向bfs的思路主要是把原串和目标串重新映射一下,使目标串被归约到九种情况,用正常的逆向bfs就可以做了(写完以后反过来一想感觉逆向是没有必要的,规约初始串是一样的。。。)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
const LL mod=1e9+7;
const double eps=1e-9;
string Input;
char str[10];
int dx[4]={1,0,0,-1};
int dy[4]={0,-1,1,0};
char nxtdir[5]="dlru";
char predir[5]="urld";
int nxt[10][500000];
int op[10][500000];
int vis[10][500000];
int pos[10];
struct node{
    char mp[10];
    int x,y,d;
};
int cantor(char *s){
    int ans=0;
    for(int i=0;i<8;++i){
        for(int j=i+1;j<9;++j)if(s[j]<s[i])ans++;
        ans*=(8-i);
    }
    return ans;
}
int trans(int x,int y){
    return 3*(x-1)+y-1;
}
void bfs(int id,node st){
    int sthsh=cantor(st.mp);
    nxt[id][sthsh]=-1;st.d=0;
    queue<node>mq;
    mq.push(st);vis[id][sthsh]=st.d;
    while(!mq.empty()){
        node nw=mq.front();
        mq.pop();int prehsh=cantor(nw.mp);
        for(int i=3;i>=0;i--){
            node pp=nw;
            int nxtx=pp.x+dx[i];
            int nxty=pp.y+dy[i];
            if(nxtx<1||nxtx>3||nxty<1||nxty>3)continue;
            swap(pp.mp[trans(pp.x,pp.y)],pp.mp[trans(nxtx,nxty)]);
            int hsh=cantor(pp.mp);
            pp.x=nxtx,pp.y=nxty;pp.d++;
            if(vis[id][hsh]==-1||(vis[id][hsh]==pp.d&&i>op[id][hsh])){op[id][hsh]=i;nxt[id][hsh]=prehsh;}
            if(vis[id][hsh]==-1){
                vis[id][hsh]=pp.d;
                mq.push(pp);
            }
        }
    }
}
node Createnode(int x){
    node p;int k=1;
    for(int i=0;i<9;i++){
        if(i!=x)p.mp[i]=k++;
    }p.mp[x]=k;
    p.x=x/3+1;
    p.y=x%3+1;
    return p;
}
void print(int id,int q){
    if(nxt[id][q]==-1){cout<<endl;return ;}
    cout<<predir[op[id][q]];
    print(id,nxt[id][q]);
}
void store(string p,node &s){
    int k=0;
    for(int i=0;i<Input.length();i++){
        if(Input[i]=='X'){
            Input[i]='9';
            s.x=k/3+1;
            s.y=k%3+1;
        }
        if(Input[i]==' ')continue;
        s.mp[k++]=Input[i]-'0';
    }
}
void init(){
    memset(vis,-1,sizeof vis);
    for(int i=0;i<9;i++){
        bfs(i,Createnode(i));
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    init();
    int T;
    cin>>T;
    node st,ed;
    for(int cas=1;cas<=T;cas++){
        cin>>Input;store(Input,st);
        cin>>Input;store(Input,ed);
        int pp=0,kk=1;
        for(int i=0;i<9;i++){
            if(ed.mp[i]==9){pp=i;continue;}
            pos[ed.mp[i]]=kk++;
        }
        for(int i=0;i<9;i++){
            if(st.mp[i]==9)continue;
            st.mp[i]=pos[st.mp[i]];
        }
        int hsh=cantor(st.mp);
        cout<<"Case "<<cas<<": "<<vis[pp][hsh]<<endl;
        print(pp,hsh);
    }
    return 0;
}

<think>嗯,用户让我解释HDU2041超级楼梯问题的C语言代码逻辑。首先,我需要回忆一下这个题目的内容。超级楼梯应该是一个经典的递归或动态规划问题,可能涉及斐波那契数列,因为每次只能走1或2步,所以到达第n级的方法数是前两级的方法数之和。 接下来看看用户提供的代码。代码里有一个函数setstairs,预先计算了楼梯数,然后在main函数里读取输入并输出结果。首先,我需要确认这段代码是否真的使用了斐波那契数列的思想。代码中的stairs数组被初始化,stairs[0]=0,stairs[1]=1,stairs[2]=2。然后从i=3开始循环,temp = s1 + s2,s1和s2分别更新,然后stairs[i] = s2。这里看起来像是动态规划的方式,用两个变量s1和s2来保存前两步的值,避免重复计算,这样时间复杂度是O(n),比递归高效。 用户可能对这段代码的具体实现细节有疑问,比如为什么用unsigned long long类型,可能是为了防止数值过大溢出。另外,函数setstairs预先计算到N=30,这样在多次查询时可以直接查表,提高效率。主函数中使用while循环处理多个输入,直到EOF结束,这也是常见的处理多组测试用例的方法。 用户可能不太明白代码中的变量s1和s2是如何更新的,以及为什么stairs[i]等于s2。需要解释每次迭代中,s1代表前前项(i-2),s2代表前一项(i-1),所以当前项i的方法数是s1+s2,然后更新s1和s2为新的前两项。这样stairs数组保存的是每个i对应的s2,因为每次迭代后s2已经更新为当前的结果。 另外,可能需要解释代码中的初始化部分,比如为什么stairs[0]是0,因为题目中的楼梯是从第1级开始的,到达第0级没有意义。而楼梯1和2的情况分别是1和2种方法,符合斐波那契的初始条件。 用户可能还关心这段代码的应用场景,比如在编程竞赛中如何处理类似的动态规划问题,或者如何优化递归解法为迭代解法。此外,代码中的预处理和查表方法对于多测试用例的情况非常有效,这也是竞赛编程中常用的技巧。 最后,需要确保解释清晰,逐步分解代码的每个部分,并联系到斐波那契数列的原理,让用户能够理解动态规划在此问题中的应用,以及代码如何高效地计算出结果。</think>### HDU2041超级楼梯C语言代码解析 让我们逐步分析这个楼梯问题的实现逻辑,涉及动态规划思想和空间优化策略。 #### 一、核心算法原理 问题本质是斐波那契数列的变形:到达第$n$级台阶的方法数等于$f(n)=f(n-1)+f(n-2)$,其中: - $f(1)=1$(直接上1级) - $f(2)=2$(1+1或直接上2级) #### 二、代码结构解析 ```c #include <stdio.h> typedef unsigned long long ULL; #define N 30 ULL stairs[N+1]; ``` 1. `typedef unsigned long long ULL`:使用无符号长整型防止数据溢出 2. `#define N 30`:题目要求的最大台阶数限制 3. `ULL stairs[N+1]`:存储预处理结果的数组 #### 三、预处理函数分析 ```c void setstairs(int n) { ULL s1=1, s2=2, temp; stairs[0] = 0; stairs[1] = 1; stairs[2] = 2; for(int i=3; i<=n; i++) { temp = s1 + s2; // 计算新值 s1 = s2; // 更新前前项 s2 = temp; // 更新前项 stairs[i] = s2; // 存储结果 } } ``` 这里采用空间优化策略: - 仅使用`s1`、`s2`两个变量代替数组,空间复杂度$O(1)$ - 时间复杂度$O(n)$,比递归实现$O(2^n)$高效 #### 四、主函数逻辑 ```c int main(void) { int n; setstairs(N); // 预处理所有可能情况 while(scanf("%d", &n) != EOF) printf("%lld\n", stairs[n]); return 0; } ``` 1. 预处理策略:预先计算所有可能输入值(1-30级) 2. 查询复杂度$O(1)$:直接查表输出结果 3. 多组输入处理:`while(scanf...)`结构适用于ACM竞赛输入模式[^1] #### 五、关键优化点 1. **空间优化**:不使用完整数组存储中间结果 2. **预处理机制**:避免重复计算(特别适合多测试用例场景) 3. **数据类型选择**:`unsigned long long`确保能存储$f(30)=2178309$的数值
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值