华容道题解 NOIP2013 思路题!

第一次发紫题题解,居然在发布前太激动,把刚写好的还没发布的题解一个Ctrl+A和Backspace全删了。(所以这是二稿)

luogu题目传送门

前置:

做本题一定要有的一些思想:

1、从简思想: 模拟白格子的移动,而千万不要想这去模拟众棋子的移动。这样会简单很多,否则会s的很惨。

2、转换思想(万物皆有其对立面):题目中给的规则,是棋子可以移动到白色格子上。我们将其共轭一下:

白色格子可以移动到棋子上,或者说,白色格子可以和棋子交换位置。

3、白色格子永远只有一个。(看似是废话的大实话)

4、习惯把起点叫做x,终点叫作ht

暴力那些事:

暴力不难实现,就是令空白格子到处移动,然后不断对ans取min就好了。别的题解有很多详细介绍,

在此不过多赘述。

思路获取:

First,从题目基本的信息入手。

如何才能令我们的目标棋子移动?

稍作思考。。。。。。当然是让空白格子在目标旗子旁边啊!!

一个要点get。

Second, 对白色格子全局的移动进行试验:

发现,白色格子的移动可以大致分为两个阶段:不顾一切移向目标棋子,然后在棋子周围打转(因为这样才可以步数最少)

だから(So),我们重点关注的对象,自然就来到了第二阶段上。

不难发现,无论棋子在哪个坐标上,白色格子永远都只会有四种情况:上,下,左,右。

移动后呢?    发现:  从一次移动到下一次移动,白色格子和棋子只会出现两大类移动:

1、白色格子还在棋子周围,只不过是上下左右随机。

2、白色格子直接和棋子位置进行了交换。

再看题目要求:最短路径。

那么,对于第一种情况,我们完全可以用BFS,求出白色格子从上一次位置移动到目前位置的距离。

第二种情况,步数很明显是1.

又一个要点get.

现在考虑,如何对我们的状态进行记录?

考虑用三维数组ok[x][y][k] (坐标x,y + 状态编号k)记录。 (其中k:k = 0,1,2,3,分别对应上下左右即可)

最后,再看一下题目的数据范围:我们完全可以求出(以棋子为中心)所有坐标到相邻坐标的步数。

!!!关系如此密集,何不考虑建图呢?

如果将所有的状态看做一个点,那么他到下一个状态的边权就是他们的步数。如果我们对这个图进行SPFA呢?

在那一串dist中,我们要求的距离,不正是终点ht周围的4个状态对应的dist吗!(取min,废话)

重大要点get!!

At last, 如何对这些状态进行编号,跑SPFA?不能直接用三维数组建图啊!

你需要一个公式:((x - 1) * m + y) * 4 - (4 - k);

至此,这道题也是被我们攻陷了。

后置:

1、一定要注意代码中的细节操作(特别是BFS跑状态的时候)

2、k对应的状态可千万不能忘呀,对应错了后果很严重呀!

码力全开!

#include <bits/stdc++.h>
using namespace std;
#define N 50
#define N2 5005
#define isdigit(c) ((c)>='0'&&(c)<='9')
#define INF (~0u>>1)
const int orz = 0;

inline int read(){
    int x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-') s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

bool ma[N][N], vis[N][N];
int fx[4] = {-1, 1, 0, 0};
int fy[4] = {0, 0, -1, 1};
int n, m, ex, ey, sx, sy, htx, hty;

inline bool judge(int x, int y){
    if(x < 1 || y < 1 || x > n || y > m)return 0;
    return ma[x][y] ;
}

inline int getnum(int x, int y, int t){
    return ((x - 1) * m + y) * 4 - (4 - t);
} 

struct data{
    int x ,y;
    int step; /*存储每个坐标的信息*/
};
/* sx,sy是否可以到达htx, hty?*/
int bfs(int dx, int dy, int sx, int sy, int htx, int hty){ 
    queue <data> q;                   /*模拟可移动格子的移动*/
    memset(vis, 0, sizeof(vis));
    q.push((data){sx, sy, 0});
    vis[sx][sy] = 1; /*从起点开始*/
    while(!q.empty()){
        data now = q.front();
        q.pop();
        int x = now.x, y = now.y, step = now.step;
        if(x == htx && y == hty){
            return now.step;   /*如果到达终点*/
        }
        for(int i = 0;i < 4; i++){
            int l = x + fx[i], r = y + fy[i];
            if(judge(l, r)){
                if(vis[l][r] || (l == dx && r == dy))continue; /*如果新点已经访问过或者等于现在的*/
                q.push((data){l, r, step + 1});
                vis[l][r] = 1;
            }
        }
    }
    return INF;        /*两个点不能互相到达*/
}

struct node{
    int u, v, w;
    int next;
} t[N2];
int f[N2];

int bian = 0;
void add(int u, int v, int w){
    t[++bian].u = u;
    t[bian].v = v;
    t[bian].w = w;
    t[bian].next = f[u];
    f[u] = bian;
    return ;
}

/*预处理, 找出所有可行状态*/

bool ok[N][N][5];
void prepare(){     
    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            if(ma[i][j])
                for(int k = 0;k < 4; k++)
                    if(judge(i+fx[k], j+fy[k]))
                        ok[i][j][k] = 1;
    for(int i = 1;i <= n; i++)    /*空白格子围绕棋子旋转时*/
        for(int j = 1;j <= m; j++)
            for(int k = 0;k < 4; k++)
                for(int l = k + 1; l < 4; l++)  /*枚举不同的方向*/
                    if(ok[i][j][k] && ok[i][j][l]){
                        int a = getnum(i, j, k), b = getnum(i, j, l);
                        int c = bfs(i, j, i + fx[k], j + fy[k], i + fx[l], j + fy[l]);
                        if(c != INF){     /*状态之间是否可达*/
                            add(a, b, c);
                            add(b, a, c);
                        } 
                    }
    for(int i = 1;i <= n; i++)  /*空白格子和棋子交换位置*/ 
        for(int j = 1;j <= m; j++){
            if(ok[i][j][3] && ok[i][j+1][2]){
                int a = getnum(i, j, 3);     /*几种小情况*/
                int b = getnum(i, j+1, 2);
                add(a, b, 1);
                add(b, a, 1); 
            }
        }
    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++){
            if(ok[i][j][1] && ok[i+1][j][0]){
                int a = getnum(i, j, 1);
                int b = getnum(i+1, j, 0);
                add(a, b, 1);
                add(b, a, 1);
            }
        }
    return ;
} 

/*进行最短路求解*/ 

queue <int> q;
bool viss[N2];
int d[N2];

int spfa(){
    memset(d, 127, sizeof(d));
    memset(viss, 0, sizeof(viss));
    int flag = d[2333]; /*之后判断用的*/
    for(int i = 0;i < 4; i++){   /*先对起点进行选取*/
        int l = sx + fx[i], r = sy + fy[i];
        if(judge(l, r)){
            int temp = bfs(sx, sy, ex, ey, l, r); /*ex,ey 是否可以到达 l,r? */
            if(temp != INF){
                int snum = getnum(sx, sy, i);
                viss[snum] = 1;
                q.push(snum);
                d[snum] = temp;
            }
        }
    }
    while(!q.empty()){   /*求所有状态间的最短路*/
        int now = q.front(); q.pop();
        viss[now] = 0;
        for(int i = f[now]; i;i = t[i].next){
            int u = t[i].u, v = t[i].v, w = t[i].w;
            if(d[v] > d[u] + w){
                d[v] = d[u] + w;
                if(!viss[v]){
                    viss[v] = 1;
                    q.push(v); 
                } 
            }
        }
    }
    int ans = INF;
    for(int i = 0; i < 4; i++){
        int num = getnum(htx, hty, i);
        ans = min(ans, d[num]);
    }
    return ans == flag ? -1 : ans;
}

int main(){
//    freopen("hh.txt", "r", stdin);
    n = read(), m = read();
    int T = read();
    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            ma[i][j] = read();
    prepare();
    while(T--){  /*e: 空白  s: 起点  ht:  终点*/
        ex = read(), ey = read(), sx = read(), sy = read(), htx = read(), hty = read();
        if(sx == htx && sy == hty){  /*特判*/
            puts("0");
            continue;
        }
        if(!ma[htx][hty] || !ma[sx][sy]){ /*如果起点终点根本不在图内*/
            puts("-1");
            continue;
        }
        printf("%d\n", spfa());
    }
    return orz; /*向大佬势力低头 同时拜一下CCf求AC*/
}

(二稿真累)

猜你喜欢

转载自www.cnblogs.com/wondering-world/p/12723169.html