[NOIP2013]华容道

YTY说:“要有题解!” 于是,我就来写了
题目链接CodeVS 3290

题目大意
在一个 N×M ( N,M30 ) 的华容道棋盘里面,每一个棋子都是 1×1 的大小,有些格子是无法移动的。对于一个给出棋盘,有 Q ( Q500 ) 个询问 : 给出空白格(B),起点(S),终点(E),问最少步数。

分析
首先,让我们看看要怎么玩华容道 :
1. 把空白格子移动到S附近(多种决策,不经过S);
2. 把S移动到空白格内,如果S到达E,则结束;
3. 把空白格移动到下一个地方(多种决策,且在S移动后的格子附近),回到2。
显然,可以暴力 O(QN4) , 显然,超时(60分)。
我们发现其实每次BFS都会做很多重复的搜索,于是我们可以先做一个预处理:对于每一个点,BFS预处理空白格子从他的i方向移动到j方向 0i,j<4 的最少步数,这样就可以跳过3的搜索。
之后,对于每一个询问 O(N2) 的BFS + SPFA得出答案,复杂度 O(N4+QKN2)

上代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <utility>
using namespace std ;

const int K = 4 ;
const int N = 30 + 2 ;
const int INF = 0x3f3f3f3f ;
const int dir[ K ][ 2 ] = { 1 , 0 , -1 , 0 , 0 , 1 , 0 , -1 } ; // 偏移,dir[0],dir[1]反向 dir[2],dir[3]反向

int n, m, q ;
bool plat[ N ][ N ] ;
int move[ N ][ N ][ K ][ K ] ; // 空白格从i方向到j方向的最短距离

struct node_move {
    int x, y ;
    int last ; // 空白格子对于此坐标的方向
    inline void scan() {
        scanf( "%d %d", &x, &y ) ;
    }
    inline node_move move( int to ) {
        node_move b = { x + dir[ to ][ 0 ] , y + dir[ to ][ 1 ] , to ^ 1 } ;
        return b ;
    } // 把此状态向to方向移动,返回得到的新状态(注意新状态的空白格)
    inline bool operator == ( const node_move a ) const {
        if ( x == a.x && y == a.y ) return true ;
        else    return false ;
    }
} B, S, E ;

#define Movv( a ) movv[ a.x ][ a.y ]
#define Bokk( a ) bokk[ a.x ][ a.y ]
#define Plat( a ) plat[ a.x ][ a.y ]
int movv[ N ][ N ] ;
bool bokk[ N ][ N ] ;
queue <node_move> Q1 ;
queue <node_move> Q2 ;
void init_move( int x , int y ) {
    node_move center = { x , y , 0 } ;
    if ( !Plat( center ) )  return ; // 判断格子是否合法
    for ( int i = 0 ; i < K ; i ++ ) {
        node_move temp = center.move( i ) ;
        if ( !Plat( temp ) )    continue ;
        Q1.push( temp ) ;
    } // 加入四个方向,注意合法
    while ( !Q1.empty() ) {
        // 对每一个Q1中的点进行BFS,求到center四周的步数
        node_move temp = Q1.front() ; Q1.pop() ;
        memset( bokk , 0 , sizeof( bokk ) ) ;
        memset( movv , 0x3f , sizeof( movv ) ) ;
        Movv( temp ) = 0 ; Q2.push( temp ) ; Bokk( temp ) = true ;
        while ( !Q2.empty() ) {
            node_move tmp = Q2.front() ; Q2.pop() ;
            for ( int i = 0 ; i < K ; i ++ ) {
                node_move node = tmp.move( i ) ;
                if ( !Bokk( node ) && Plat( node ) && !( node == center ) ) {
                    Movv( node ) = Movv( tmp ) + 1 ;
                    Q2.push( node ) ; Bokk( node ) = true ;
                }
            }
        }
        for ( int i = 0 ; i < K ; i ++ )
                 move[ center.x ][ center.y ][ temp.last ^ 1 ][ i ] = Movv( center.move( i ) ) ;
        // temp.last^1 方向才是每次BFS起点!! (画重点)
    }
}
void init() {
    memset( move , 0x3f , sizeof( move ) ) ;
    scanf( "%d %d %d", &n, &m, &q ) ;
    for ( int i = 1 ; i <= n ; i ++ )
        for ( int j = 1 ; j <= m ; j ++ ) {
            int a ; scanf( "%d", &a ) ;
            if ( a )    plat[ i ][ j ] = true ;
            else    plat[ i ][ j ] = false ;
        }
    for ( int i = 1 ; i <= n ; i ++ )
        for ( int j = 1 ; j <= m ; j ++ )
            init_move( i , j ) ; // 预处理
}

// 每个node_move都相当与是图中的一个点,用dist记录距离和book记录入队
#define Dist( a ) dist[ a.x ][ a.y ][ a.last ]
#define Book( a ) book[ a.x ][ a.y ][ a.last ]
#define Move( a , b ) move[ a.x ][ a.y ][ a.last ][ b ]
int dist[ N ][ N ][ K ] ;
bool book[ N ][ N ][ K ] ;
void figure() {
    for ( int i = 1 ; i <= q ; i ++ ) {
        B.scan(), S.scan(), E.scan() ;
        if ( !Plat( S ) || !Plat( E ) ) {
            puts( "-1" ) ; continue ;
        } // 起点和终点是否合法(貌似多余)
        if ( S == E ) {
            puts( "0" ) ; continue ;
        } // 特判
        memset( bokk , 0 , sizeof( bokk ) ) ;
        memset( book , 0 , sizeof( book ) ) ;
        memset( movv , 0x3f , sizeof( movv ) ) ;
        memset( dist , 0x3f , sizeof( dist ) ) ;
        Movv( B ) = 0 ; Q1.push( B ) ; Bokk( B ) = true ;
        while ( !Q1.empty() ) {
            node_move temp = Q1.front() ; Q1.pop() ;
            for ( int j = 0 ; j < K ; j ++ ) {
                node_move node = temp.move( j ) ;
                if ( !Bokk( node) && Plat( node ) && !( node == S ) ) {
                    Movv( node ) = Movv( temp ) + 1 ;
                    Q1.push( node ) ; Bokk( node ) = true ;
                }
            }
        } // BFS从B到S四周(不经过S)距离
        for ( int j = 0 ; j < K ; j ++ ) {
            if ( !Plat( S.move( j ) ) ) continue ;
            Dist( S.move( j ) ) = Movv( S.move( j ) ) + 1 ;
            Q1.push( S.move( j ) ) ; Book( S.move( j ) ) = true ;
        } // 加入从S开始的决策
        while ( !Q1.empty() ) {
            node_move temp = Q1.front() ; Q1.pop() ;
            for ( int j = 0 ; j < K ; j ++ ) {
                node_move node = temp.move( j ) ;
                if ( !Plat( node ) )    continue ;
                if ( Dist( node ) > Dist( temp ) + Move( temp , j ) + 1 ) {
                    if ( !Book( node ) )    Book( node ) = true ;
                    Q1.push( node ) ;
                    Dist( node ) = Dist( temp ) + Move( temp , j ) + 1 ;
                }
            }
        } // SPFA 起始状态到终点状态的最短路
        int ans = INF ;
        for ( int j = 0 ; j < K ; j ++ ) {
            node_move temp = (node_move){ E.x , E.y , j } ;
            ans = min( ans , Dist( temp ) ) ;
        } // 可以从多个方向移动到E
        if ( ans == INF )   ans = -1 ;
        printf( "%d\n", ans ) ;
    }
}
int main() {
    init() ;
    figure() ;
    return 0 ;
}

以上

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值