HDU 1010 Tempter of the Bone【经典DPS】

Tempter of the Bone

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 90627    Accepted Submission(s): 24657


Problem Description
The doggie found a bone in an ancient maze, which fascinated him a lot. However, when he picked it up, the maze began to shake, and the doggie could feel the ground sinking. He realized that the bone was a trap, and he tried desperately to get out of this maze.

The maze was a rectangle with sizes N by M. There was a door in the maze. At the beginning, the door was closed and it would open at the T-th second for a short period of time (less than 1 second). Therefore the doggie had to arrive at the door on exactly the T-th second. In every second, he could move one block to one of the upper, lower, left and right neighboring blocks. Once he entered a block, the ground of this block would start to sink and disappear in the next second. He could not stay at one block for more than one second, nor could he move into a visited block. Can the poor doggie survive? Please help him.
 

Input
The input consists of multiple test cases. The first line of each test case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50), which denote the sizes of the maze and the time at which the door will open, respectively. The next N lines give the maze layout, with each line containing M characters. A character is one of the following:

'X': a block of wall, which the doggie cannot enter;
'S': the start point of the doggie;
'D': the Door; or
'.': an empty block.

The input is terminated with three 0's. This test case is not to be processed.
 

Output
For each test case, print in one line "YES" if the doggie can survive, or "NO" otherwise.
 

Sample Input
   
   
   
   
4 4 5 S.X. ..X. ..XD .... 3 4 5 S.X. ..X. ...D 0 0 0
 

Sample Output
   
   
   
   
NO YES
 

Author
ZHANG, Zheng
 

Source
ZJCPC2004
 
题意:
 a第一个数是宽 b第二个数是长 c第三个数是要求从S到D的步数 
 'X'是墙不可以穿越
 'S'是出发点
 'D'是终点
 若c步能够从S到D则输出YES 否则输出NO
 以0 0 0结束程序
 
注意:
1:借鉴了这位大神 http://acm.hdu.edu.cn/forum/read.php?tid=6158
学到了新的东西 比如 第七条中的 迷宫图的 奇偶剪枝 和 路径剪枝 N*M-wall<=T时 节约了大量时间从976MS减到不到100MS 好棒~(PS:不要笑本弱菜~~)
文章内容如下:

1010 temp of the bone

sample input:
4 4 5
S.X.
..X.
..XD
....

问题:
(1):
在发现当前节点无法到达时,这点弹出栈,并且把这点的标记重新刷为'.'

(2):
如何在dfs中既要保证到达又要使时间正好呢?? 在函数中通过这种形式实现:

dfs(int si,int sj,int cnt) 就是用cnt来记录当时的时间,并且在

if( si==di && sj==dj && cnt==t )
    {
        escape = 1;
        return;
    }

的时候 即当前点到达了终点并且时间恰好等于题目所给限制时间时,跳出
并且escape标记为真

(3):
如何让一个点有顺序地遍历它四周地能到达的点呢??
聪明并且简短的方法是设施一个dir[4][2] 数组 控制方向
并且设置它的值为dir[4][2]={{0,-1},{0,1},{1,0},{-1,0}};

遍历的时候用for(i:0->4)就非常方便了

(4):
千万要注意的是节点越界的情况, dfs(int si,int sj,int cnt)的时候一定要把 si, sj 控制在给你的矩阵内 在后面会提到一个我的列子 就是因为访问了[0, -1]的位置导致了其

他数据被更改

(5):
读入矩阵的时候,可以采用for(i = 1; i <= N; i++)
               for(j = 1; j <= M; j++)
                scanf("%c", &map[i][j]);       

的方法,好处在于可以控制和计算每一个读入的数据,坏处是调试的时候对矩阵的观察不太方便,而且好像还会有错误,在2102"A计划"用这种方法读入数据时好像就会wa,

另一种方法是for(i = 0; i < N; i++) gets(map[i]);
这样读入的数据在调试观察的时候十分方便 gets()读入的默认为字符串,在vc调试的时候是显式的 可以直接观察矩阵 缺点是对矩阵中各个数据的计算和控制无法实现,需要读完后再遍历一遍

(6)
能用bfs还是尽量用bfs 我不会bfs.... dfs的递归在调试的时候不是很方便,而且bfs要比dfs快,调试也要方便,因为它没有递归

(7)
关于剪枝,没有剪枝的搜索不太可能,这题老刘上课的时候讲过两个剪枝,一个是奇偶剪枝,一个是路径剪枝

奇偶剪枝:
把矩阵标记成如下形式:
0,1,0,1,0
1,0,1,0,1
0,1,0,1,0
1,0,1,0,1
很明显,如果起点在0 而终点在1 那显然 要经过奇数步才能从起点走到终点,依次类推,奇偶相同的偶数步,奇偶不同的奇数步
在读入数据的时候就可以判断,并且做剪枝,当然做的时候并不要求把整个矩阵0,1刷一遍,读入的时候起点记为(Si,Sj) 终点记为(Di,Dj) 判断(Si+Sj) 和 (Di+Dj) 的奇偶性就可以了

路径剪枝:
矩阵的大小是N*M 墙的数量记为wall 如果能走的路的数量 N*M - wall 小于时间T,就是说走完也不能到总的时间的,这显然是错误的,可以直接跳出了

课件里面给过这题的标程,在dfs的过程中有个没提到的剪枝,就是记录当前点到终点的最短路,如果小于剩余的时间的话,就跳出
这个剪枝我觉得更科学,它毕竟是动态的么,标程里面是这么写的:
temp = (t-cnt) - abs(si-di) - abs(sj-dj);
if( temp<0 || temp&1 ) return;

其中求当前点到终点的最短路是这样 abs(si-di) - abs(sj-dj) 这个就比较粗糙了 明显没有考虑到碰到墙要拐弯的情况
那求最短路有没有什么好办法呢?

我曾经想到过用 Dijkstraq求最短路的 ,明显大才小用,在论坛里看到一个方法觉得可以用在这里
给定下例:

S.X.
..X.
..XD
....

每个点到终点的最短路是不是这样呢:

S6X2
65X1
54XD
4321

这怎么求呢??从终点开始遍历整个数组,终点是0,它周围的点都+1,墙就不计数,依次类推,就能求得这个矩阵的一个最短时间矩阵,在dfs的时候比较当前点到终点的最短路,如果大于剩余时间的话就跳出

这个方法的预处理还是非常快的,我没有用过,但是感觉会非常有用处.

(8)
在做这题的时候,我碰到过一个神奇的事情,在程序运行至下面代码时

if( map[ si+dir[i][0] ][ sj+dir[i][1] ] != 'X')           
    map[ si+dir[i][0] ][ sj+dir[i][1] ] = 'X';

T被改变了!! 这丝毫和T没有关系啊,怎么改变T的值呢??

原来在起点map[0][0]进入时,我没有注意到map[ si+dir[i][0] ][ sj+dir[i][1] ] 实际做的是map[0][-1] = 'X'; 很危险的一个赋值,书本上千万次强调的东西让我碰上了,这个地方我找了很久,因此我觉得有必要单独列出来提醒自己

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面我把一个带注释的标程贴一下,不是我写的注释


//zju 2110 Tempter of the Bone
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>

using namespace std;

//迷宫地图
//X: 墙壁,小狗不能进入
//S: 小狗所处的位置
//D: 迷宫的门
//. : 空的方格
char map[9][9];
int n,m,t,di,dj; //(di,dj):门的位置
bool escape;
int dir[4][2]={{0,-1},{0,1},{1,0},{-1,0}}; //分别表示下、上、左、右四个方向

void dfs(int si,int sj,int cnt)  //表示起始位置为(si,sj),要求在第cnt秒达到门的位置
{
    int i,temp;
    if( si>n || sj>m || si<=0 || sj<=0 ) return;
   
    if( si==di && sj==dj && cnt==t )
    {
        escape = 1;
        return;
    }
   
    //abs(x-ex) + abs(y - ey)表示现在所在的格子到目标格子的距离(不能走对角线)
    //t-cnt是实际还需要的步数,将他们做差
    //如果temp < 0或者temp为奇数,那就不可能到达!
    temp = (t-cnt) - abs(si-di) - abs(sj-dj);
   
    if( temp<0 || temp&1 ) return;
   
    for( i=0; i<4; i++ )
    {
        if( map[ si+dir[i][0] ][ sj+dir[i][1] ] != 'X')
        {
            map[ si+dir[i][0] ][ sj+dir[i][1] ] = 'X';
               
                dfs(si+dir[i][0], sj+dir[i][1], cnt+1);
           
            if(escape) return;
           
            map[ si+dir[i][0] ][ sj+dir[i][1] ] = '.';
        }
    }
   
    return;
}

int main()
{
    int i,j,si,sj;
   
    while( cin >> n >> m >> t)
    {
        if( n==0 && m==0 && t==0 )
            break;
   
        int wall = 0;
        for( i=1; i<=n; i++ )
            for( j=1; j<=m; j++ )
            {
                cin >> map[i][j];
                if(map[i][j]=='S') { si=i; sj=j; }
                else if( map[i][j]=='D' ) { di=i; dj=j; }
                else if( map[i][j]=='X' ) wall++;
            }
           
            if( n*m-wall <= t )
            {
                cout << "NO" << endl;
                continue;
            }
           
            escape = 0;
            map[si][sj] = 'X';
           
            dfs( si, sj, 0 );
           
            if( escape ) cout << "YES" << endl;
            else cout << "NO" << endl;
    }
   
    return 0;
}

这个注释当初还是帮了我很大忙的,起码让我看懂了课件



 

 
#include<stdio.h>
#include<string.h>
#include<stdbool.h>
char map[10][10],step[4][2]= {{-1,0},{0,1},{1,0},{0,-1}};
int a,b,T,ax,ay,bx,by,flag;
int pos(int p)
{
    return p>0?p:-p;
}
void DFS(int x,int y,int t)
{
    int temp;
     /*if(map[x][y]=='0')//至今仍然不知道为什么不能这样写 超出边界的情况
     {
         return;
     }*/
    if( x>a|| y>b || x<=0 || y<=0 ) return;//只能这样写
    if(t==T&&x==bx&&y==by)//好像也不能写成if(t==T&&map[x][y]=='D')
    {
        flag=1;
        return ;
    }
    temp=(T-t)-pos(x-bx)-pos(y-by);//路径剪枝
    if(temp<0||temp%2==1)
    {
        //printf("! ");
        return ;
    }
    for(int i=0; i<4; i++)
    {
        if(map[x+step[i][0]][y+step[i][1]]!='X')
        {
            map[x+step[i][0]][y+step[i][1]]='X';
            DFS(x+step[i][0],y+step[i][1],t+1);
            map[x+step[i][0]][y+step[i][1]]='.';//类似于回溯标记
            if(flag) return ;
        }
    }
}
int main (void)
{
    int wall;
    int i,ii;
    while(~scanf("%d%d%d",&a,&b,&T),a||b||T)
    {
        wall=0;
        flag=0;
        memset(map,'0',sizeof(map));
        getchar();//一定要注意 %c 清流
        for( i=1; i<=a; i++)
        {
            for(ii=1; ii<=b; ii++)
            {
                scanf("%c",&map[i][ii]);
                if(map[i][ii]=='S')
                {
                    ax=i;
                    ay=ii;
                }
                else if(map[i][ii]=='X')
                {
                    wall++;
                }
                else if(map[i][ii]=='D')
                {
                    bx=i;
                    by=ii;
                }
            }
            getchar();//每次回车清流 
        }
        if(a*b-wall<=T)//新技能 节约了很多时间 很棒的方法
        {
            printf("NO\n");
            continue;
        }
        map[ax][ay]='X';
        DFS(ax,ay,0);
        if(flag==1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
/*
4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
*/

 
一些总结
 
//62ms
#include<stdio.h>
#include<string.h>
#include<stdbool.h>
char map[10][10],step[4][2]= {{-1,0},{0,1},{1,0},{0,-1}};
int a,b,T,ax,ay,bx,by,flag;
int pos(int p)
{
    return p>0?p:-p;
}
void DFS(int x,int y,int t)
{
    int temp;
     /*if(map[x][y]=='0')//至今仍然不知道为什么不能这样写 超出边界的情况
     {
         return;
     }*/
    if( x>a|| y>b || x<=0 || y<=0 ) return;
    if(t==T&&x==bx&&y==by)//好像也不能写成if(t==T&&map[x][y]=='D')
    {
        flag=1;
        return ;
    }
    temp=(T-t)-pos(x-bx)-pos(y-by);//路径剪枝
    if(temp<0||temp%2==1)
    {
        //printf("! ");
        return ;
    }
    for(int i=0; i<4; i++)
    {
        if(map[x+step[i][0]][y+step[i][1]]!='X')
        {
            map[x+step[i][0]][y+step[i][1]]='X';
            DFS(x+step[i][0],y+step[i][1],t+1);
            map[x+step[i][0]][y+step[i][1]]='.';
            if(flag) return ;
        }
    }
}
int main (void)
{
    int wall;
    int i,ii;
    while(~scanf("%d%d%d",&a,&b,&T),a||b||T)
    {
        wall=0;
        flag=0;
        memset(map,'0',sizeof(map));
        getchar();
        for( i=1; i<=a; i++)
        {
            for(ii=1; ii<=b; ii++)
            {
                scanf("%c",&map[i][ii]);
                if(map[i][ii]=='S')
                {
                    ax=i;
                    ay=ii;
                }
                else if(map[i][ii]=='X')
                {
                    wall++;
                }
                else if(map[i][ii]=='D')
                {
                    bx=i;
                    by=ii;
                }
            }
            getchar();
        }
        if(a*b-wall<=T)//新技能 节约了很多时间
        {
            printf("NO\n");
            continue;
        }
        map[ax][ay]='X';
        DFS(ax,ay,0);
        if(flag==1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
/*
4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
*/
//62MS
#include<stdio.h>
#include<string.h>
#include<stdbool.h>
char map[10][10],step[4][2]= {{-1,0},{0,1},{1,0},{0,-1}};
int a,b,T,ax,ay,bx,by,flag;
int pos(int p)
{
    return p>0?p:-p;
}
void DFS(int x,int y,int t)
{
    int temp;
    /*if(map[x][y]=='0')//不明白为什么不能这样表示 要像19行那样表示
    {
        return;
    }*/
    if(flag) return;//可以不要

    if( x>a|| y>b || x<=0 || y<=0 ) return;//边界剪枝

    temp=(T-t)-pos(x-bx)-pos(y-by);//路径剪枝
    if(temp<0||temp%2==1)
    {
        //printf("! ");
        return ;
    }
    if(t==T&&x==bx&&y==by)//END
    {
        flag=1;
        return ;
    }


    for(int i=0; i<4; i++)
    {
        if(map[x+step[i][0]][y+step[i][1]]!='X')
        {
            map[x+step[i][0]][y+step[i][1]]='X';
            DFS(x+step[i][0],y+step[i][1],t+1);
            map[x+step[i][0]][y+step[i][1]]='.';
            if(flag) return ;
        }
    }
}
int main (void)
{
    int wall;
    int i,ii;
    while(~scanf("%d%d%d",&a,&b,&T),a||b||T)
    {
        wall=0;
        flag=0;
        memset(map,'0',sizeof(map));
        getchar();
        for( i=1; i<=a; i++)
        {
            for(ii=1; ii<=b; ii++)
            {
                scanf("%c",&map[i][ii]);
                if(map[i][ii]=='S')
                {
                    ax=i;
                    ay=ii;
                }
                else if(map[i][ii]=='X')
                {
                    wall++;
                }
                else if(map[i][ii]=='D')
                {
                    bx=i;
                    by=ii;
                }
            }
            getchar();
        }
        if(a*b-wall<=T)//节约600MS
        {
            printf("NO\n");
            continue;
        }
        map[ax][ay]='X';
        DFS(ax,ay,0);
        if(flag==1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
/*
4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
*/

//上面的方法62MS
void DFS()
{
    if()//边界剪枝
    ;
    if()//END
    ;
    if()//路径剪枝
    ;
    for(;;)//递归DFS
    {
        if("合法路线成立||不是非法路线‘墙’")
        DFS();

    }
}
//下面这种方法350MS
void DFS()
{
    if()//路径剪枝
    ;
    if()//END
    ;
    for(;;)
    {
        if()//边界剪枝①+合法路径   ①比上面那种方法浪费了多次循环的时间
        {
            DFS();
        }
    }
}
//备注:
//N*M-Wall<=T  节约了600MS 的时间

//980MS
#include<cstdio>
#include<cstring>
#include<cmath>
char map[10][10];
int vis[10][10];
int T,t[9][9],dir[4][2]= {{1,0},{0,1},{0,-1},{-1,0}},flag,di,dj;
int fun(int a)
{
    return a>0?a:-a;
}
bool CE(int x,int y)
{
    if(!vis[x][y]&&map[x][y]!='X'&&map[x][y]!='0')//正确的路径返回1
        return 1;
    else
        return 0;
}
void dfs(int x,int y)
{
    if((fun(di-x)+fun(dj-y))>(T-t[x][y])||fun(di+dj-x-y)%2!=(T-t[x][y])%2)
        return ;

    if(map[x][y]=='D'&&t[x][y]==T)
    {
        flag=1;
        return ;
    }
    for(int i=0; i<4; i++)
    {
        if(vis[x][y]==false)
        {

            if(CE(x+dir[i][0],y+dir[i][1]) && t[x][y]+1<=T)
            {
                vis[x][y]=1;
                t[x+dir[i][0]][y+dir[i][1]]=t[x][y]+1;
                dfs(x+dir[i][0],y+dir[i][1]);
                if(flag==1)
                    return ;
                vis[x][y]=0;
            }

        }
    }

}

int main()
{
    int n,m,si,sj;
    while(scanf("%d%d%d",&n,&m,&T),n||m||T)
    {
        flag=0;
        memset(vis,0,sizeof(vis));
        memset(map,'0',sizeof(map));
        memset(t,0,sizeof(t));
        getchar();
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                scanf("%c",&map[i][j]);
                if(map[i][j]=='S')
                {
                    si=i;
                    sj=j;
                }
                if(map[i][j]=='D')
                {
                    di=i;
                    dj=j;
                }
            }
            getchar();
        }
        if(fun(di-si)+fun(dj-sj)>T||fun(di+dj-si-sj)%2!=T%2)//
            printf("NO\n");
        else
        {
            dfs(si,sj);
            if(flag==1)
                printf("YES\n");
            else
                printf("NO\n");
        }
    }
    return 0;
}


你可能感兴趣的:(ACM,HDU)