【NOIP2013提高组day2】华容道

Description

给出一张n*m的棋盘,有一些点上有障碍物,其他点上都是棋子。给出q次询问,每次询问给出一个空格,一个目标棋子,一个目标位置,每一步可以把一个棋子移进空格,求把目标棋子移动到目标位置的最小步数。
n,m<=30,q<=500

Solution

第一眼看到就知道是大暴力题,发现询问次数少的时候一次bfs就解决了,那么询问多起来呢?
预处理!
如何预处理?
我们先不想这个问题,想一想询问少的时候的另一种做法,DP。
设Fx1,y1,x2,y2表示空格在(x1,y1),目标棋子在(x2,y2)的最小步数,转移显然。
我们发现,在整个DP过程中x1,y1经常改变,x2,y2却很少改变。
那我们可以改进一下DP,设Fx,y,k(k=0..3)表示目标棋子在(x,y),空格在其上(下,左,右)的最小步数。
怎么转移?
发现这样很难转移,不如把每一个状态抽象成一个点,然后向它所能转移到的状态连边。边权可以用N^2的bfs暴力搞出来。一个状态可以有两种后继。一种是把空格和目标棋子交换,边权为1;另一种就是把空格换个位置,边权自己暴力。
发现这样建出来的图和那些询问的东西无关,就可以预处理出来了。(∩_∩)
然后,对于每次询问,建立源点和汇点。(网络流打多了)从源点向每一个目标棋子周围的状态连边,边权暴力^2,每一个终止状态向汇点连边,边权为0。
然后就可以跑一边spfa 费用流来求答案了。时间飞起。
(原谅我网络流写太多了,有些神经质)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 35
#define M 4005
#define inf 0x7fffffff/3
using namespace std;
int n,m,q,x,y,s,sx,sy,tx,ty,xx,yy,ret,ans,tot,l,S,T;
int a[N][N],d[M][3],dis[M],p[M*4],v[M][M],b[M][M];
bool bz[N][N],f[M],r[4];
int g[4][2]={-1,0,1,0,0,1,0,-1};
void add(int x,int y,int z) {b[x][++b[x][0]]=y;v[x][y]=z;}
int w(int x,int y,int z) {return z*n*m+(x-1)*m+y;}
int bfs(int sx,int sy,int tx,int ty,int x,int y) {
    memset(bz,0,sizeof(bz));
    int i=0,j=1;d[1][0]=sx;d[1][1]=sy;d[1][2]=0;bz[sx][sy]=1;
    if (sx==tx&&sy==ty) return 0;
    while (i<j) {
        int px=d[++i][0],py=d[i][1];
        fo(k,0,3) {
            int dx=px+g[k][0],dy=py+g[k][1];
            if (dx<1||dx>n||dy<1||dy>m||!a[dx][dy]
            ||bz[dx][dy]
            ||(dx==x&&dy==y)) continue;
            d[++j][0]=dx;d[j][1]=dy;d[j][2]=d[i][2]+1;bz[dx][dy]=1;
            if (dx==tx&&dy==ty) return d[j][2];
        }
    }
    return -1;
}
int spfa() {
    memset(dis,127,sizeof(dis));
    int mx=dis[S];dis[S]=0;
    memset(f,0,sizeof(f));
    f[S]=1;p[1]=S;int i=0,j=1;
    while (i<j) {
        int x=p[++i];
        fo(k,1,b[x][0]) if (dis[b[x][k]]>dis[x]+v[x][b[x][k]]) {
            dis[b[x][k]]=dis[x]+v[x][b[x][k]];
            if (!f[b[x][k]]) f[b[x][k]]=1,p[++j]=b[x][k];
        }
        f[p[i]]=0;
    }
    if (dis[T]!=mx) return dis[T];else return -1;
}
int main() { 
    scanf("%d%d%d",&n,&m,&q);
    fo(i,1,n)
        fo(j,1,m) scanf("%d",&a[i][j]);
    fo(i,1,n)
        fo(j,1,m)
            if (a[i][j]) 
                fo(k,0,3) {
                    xx=i+g[k][0],yy=j+g[k][1];
                    if (xx<1||xx>n||yy<1||yy>m||!a[xx][yy]) continue;
                    if (k%2) s=k-1;else s=k+1;
                    add(w(i,j,k),w(xx,yy,s),1);
                    fo(s,0,3) {
                        if (s==k) continue;
                        int dx=i+g[s][0],dy=j+g[s][1];
                        if (dx<1||dx>n||dy<1||dy>m||!a[dx][dy]) continue;
                        ret=bfs(xx,yy,dx,dy,i,j);
                        if (ret==-1) continue;
                        add(w(i,j,k),w(i,j,s),ret);
                    }
                }
    S=0;T=4*n*m+1;
    while (q) {
        q--;scanf("%d%d%d%d%d%d",&x,&y,&sx,&sy,&tx,&ty);
        if (sx==tx&&sy==ty) {printf("0\n");continue;}
        fo(i,0,3) {
            xx=sx+g[i][0],yy=sy+g[i][1];
            if (xx<1||xx>n||yy<1||yy>m||!a[xx][yy]) continue;
            ret=bfs(x,y,xx,yy,sx,sy);
            if (ret==-1) continue;
            add(S,w(sx,sy,i),ret);
        }
        fo(i,0,3) {
            xx=tx+g[i][0],yy=ty+g[i][1];
            if (xx<1||xx>n||yy<1||yy>m||!a[xx][yy]) continue;
            add(w(tx,ty,i),T,0);r[i]=1;
        }     
        printf("%d\n",spfa());  
        b[S][0]=0;fo(i,0,3) if (r[i]) r[i]=0,b[w(tx,ty,i)][0]--;    
    }
}

为什么变黑了?!(TAT)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值