Store-keeper解题报告

· · 题解

题目描述

题目传送门

大意:给定一个箱子和指定要推到的位置,其中路径中有障碍物,最小化推动次数。

解法

首先矩阵中走最少步数我们很容易想到用dp解,这题有用信息是人和箱子的位置。

最朴素的状态描述 dp_{i,j,x,y} 表示箱子在 i,j 人在 x,y的最小步数。

很明显有冗余状态,会 MLE+TLE 要改状态定义。

我们可以观察到人的具体位置是不重要的,只要关心他在箱子的上下左右的相对位置即可。

于是有状态描述 dp_{i,j,k} 表示箱子在 i,j 人在箱子上下左右的最小步数。

我们发现对于一个箱子如果人要切换到箱子的另一个方位是不一定可行的,这就是转移的难点所在。

难点:无法确定对于一个位置 x,y 的上下左右四个格子是否能互相到达。

通过观察可以猜想,因为不能到达只可能是被箱子挡住了,所以问题变为两个点之间删去一个点还能到达要满足什么充要条件。

矩阵中的 dp 问题通常需要构图,利用一些图上算法处理。

这让我们想到了图论中关于点双连通分量的一个结论:点双连通分量中,任意两点存在没有相同点的路径。

于是我们猜想如果两点在同一点双连通分量中则可以互相到达,两点不在同一点双连通分量中则不能。

证明:我们考虑 u,v 两个点互相到达(中间隔了一个不能走的箱子所占的点,设为 t)的条件。

  1. 假设 u,v 在同一点双连通分量中显然可以。
  2. 假设 u,v 不在同一点双连通分量中,若 u,v 可以互相到达,则 u,v 中一定存在一条不经过 t 的路径。那么如果 t 可以经过 u,v 之间又会存在一条路径可互相到达,那么 u,v 一定在一个点双连通分量中,与假设矛盾。

证毕。

算法流程:

  1. bfs找初始状态。
  2. tarjan预处理每个哪些点双连通分量中(注意一条边只属于一个点双连通分量,而一个点可能却属于多个多个点双连通分量,注意要记录边在哪个分量中,而不是点)。
  3. 利用01bfs求解dp。

再说一下可以用 01bfs 求解是因为只有两种转移方式一种换方向花费为 0,一种推箱子花费为 1

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
#include<map>
#include<iostream>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=100*100+10;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
int n,m,a[110][110],M,P,K,low[maxn],dep[maxn],val[maxn],nnum,rt,dsl,ans=-1,dp[110][110][4];
char aa[110][110];
bool vis[110][110][4],can[110][110],vv[maxn],ve[maxn],vb[110][110];
vector<int>vec;
stack<int>sta;
map<int,pair<int,int> >ed;
struct node{
    int x,y,z;
    node(int x_,int y_,int z_){
        x=x_,y=y_,z=z_;
    }
    node(){}
}f[maxn];
struct edge{
    int to,nxt,num;
}e[maxn<<2];
int head[maxn],cnt=1;
void add(int u,int v,int num){
    e[cnt].to=v,e[cnt].nxt=head[u],e[cnt].num=num,head[u]=cnt++;
}
queue<node>q;
deque<node>qq;
int id(int x,int y){
    if(x<1||x>n||y<1||y>m) return -1;
    return (x-1)*m+y;
}
void fd(){
    q.push(node(f[M].x,f[M].y,f[M].z));
    vb[f[M].x][f[M].y]=1;
    while(!q.empty()){
        node now=q.front();
        q.pop();
        can[now.x][now.y]=1;
        for(int i=0;i<=3;i++){
            int nx=now.x+dx[i],ny=now.y+dy[i];
            if(nx>n||nx<0||ny>m||ny<0||aa[nx][ny]=='S'||can[nx][ny]||aa[nx][ny]=='P'||aa[nx][ny]==0||vb[nx][ny]) continue;
            vb[nx][ny]=1;
            q.push(node(nx,ny,-1));
        }
    }
}
void dfs(int u,int d){
    vv[u]=1,low[u]=dep[u]=d;
    bool flag=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(!ve[e[i].num]) sta.push(e[i].num),ve[e[i].num]=1;
        if(!vv[v]){
            dfs(v,d+1);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dep[u]){
                dsl++;
                while(sta.top()!=e[i].num)
                    val[sta.top()]=dsl,sta.pop();
                val[e[i].num]=dsl,sta.pop(); 
            }
        }
        else if(dep[v]<dep[u]-1)
            low[u]=min(low[u],dep[v]);
    }
}
bool isin(int x,int y){
    if(x==-1||y==-1) return 0;
    for(int i=head[x];i;i=e[i].nxt)
        for(int j=head[y];j;j=e[j].nxt){
            if(val[e[i].num]==val[e[j].num]) return 1;
        }
    return 0;
}
void bfs(){
    int sx=f[P].x,sy=f[P].y;
    memset(dp,0x3f,sizeof(dp));
    if(can[sx-1][sy]) qq.push_front(node(sx,sy,0)),dp[sx][sy][0]=0,vis[sx][sy][0]=1;
    if(can[sx+1][sy]) qq.push_front(node(sx,sy,1)),dp[sx][sy][1]=0,vis[sx][sy][1]=1;
    if(can[sx][sy-1]) qq.push_front(node(sx,sy,2)),dp[sx][sy][2]=0,vis[sx][sy][2]=1;
    if(can[sx][sy+1]) qq.push_front(node(sx,sy,3)),dp[sx][sy][3]=0,vis[sx][sy][3]=1;
    while(!qq.empty()){
        node now=qq.front();
        qq.pop_front();
        int x=now.x,y=now.y,z=now.z;
        if(now.x==f[K].x&&y==f[K].y){
            ans=dp[x][y][z];
            return;
        }
        if(z==0){
            if(aa[x+1][y]!=0&&aa[x+1][y]!='S'&&!vis[x+1][y][z]) 
                vis[x+1][y][0]=1,dp[x+1][y][0]=dp[x][y][0]+1,qq.push_back(node(x+1,y,z));
            if(!vis[x][y][1]&&isin(id(x-1,y),id(x+1,y))) 
                vis[x][y][1]=1,dp[x][y][1]=dp[x][y][0],qq.push_front(node(x,y,1));
            if(!vis[x][y][2]&&isin(id(x-1,y),id(x,y-1))) 
                vis[x][y][2]=1,dp[x][y][2]=dp[x][y][0],qq.push_front(node(x,y,2));
            if(!vis[x][y][3]&&isin(id(x-1,y),id(x,y+1))) 
                vis[x][y][3]=1,dp[x][y][3]=dp[x][y][0],qq.push_front(node(x,y,3));
        }
        if(z==1){
            if(aa[x-1][y]!=0&&aa[x-1][y]!='S'&&!vis[x-1][y][z]) 
                vis[x-1][y][1]=1,dp[x-1][y][1]=dp[x][y][1]+1,qq.push_back(node(x-1,y,z));
            if(!vis[x][y][0]&&isin(id(x+1,y),id(x-1,y))) 
                vis[x][y][0]=1,dp[x][y][0]=dp[x][y][1],qq.push_front(node(x,y,0));
            if(!vis[x][y][2]&&isin(id(x+1,y),id(x,y-1))) 
                vis[x][y][2]=1,dp[x][y][2]=dp[x][y][1],qq.push_front(node(x,y,2));
            if(!vis[x][y][3]&&isin(id(x+1,y),id(x,y+1))) 
                vis[x][y][3]=1,dp[x][y][3]=dp[x][y][1],qq.push_front(node(x,y,3));      
        }
        if(z==2){
            if(aa[x][y+1]!=0&&aa[x][y+1]!='S'&&!vis[x][y+1][z]) 
                vis[x][y+1][2]=1,dp[x][y+1][2]=dp[x][y][2]+1,qq.push_back(node(x,y+1,z));
            if(!vis[x][y][0]&&isin(id(x,y-1),id(x-1,y))) 
                vis[x][y][0]=1,dp[x][y][0]=dp[x][y][2],qq.push_front(node(x,y,0));
            if(!vis[x][y][1]&&isin(id(x,y-1),id(x+1,y))) 
                vis[x][y][1]=1,dp[x][y][1]=dp[x][y][2],qq.push_front(node(x,y,1));
            if(!vis[x][y][3]&&isin(id(x,y-1),id(x,y+1))) 
                vis[x][y][3]=1,dp[x][y][3]=dp[x][y][2],qq.push_front(node(x,y,3));
        }
        if(z==3){
            if(aa[x][y-1]!=0&&aa[x][y-1]!='S'&&!vis[x][y-1][z]) 
                vis[x][y-1][3]=1,dp[x][y-1][3]=dp[x][y][3]+1,qq.push_back(node(x,y-1,z));
            if(!vis[x][y][0]&&isin(id(x,y+1),id(x-1,y))) 
                vis[x][y][0]=1,dp[x][y][0]=dp[x][y][3],qq.push_front(node(x,y,0));
            if(!vis[x][y][1]&&isin(id(x,y+1),id(x+1,y))) 
                vis[x][y][1]=1,dp[x][y][1]=dp[x][y][3],qq.push_front(node(x,y,1));
            if(!vis[x][y][2]&&isin(id(x,y+1),id(x,y-1))) 
                vis[x][y][2]=1,dp[x][y][2]=dp[x][y][3],qq.push_front(node(x,y,2));              
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    memset(aa,0,sizeof(aa));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cin>>aa[i][j];
            f[id(i,j)].x=i,f[id(i,j)].y=j;
            if(aa[i][j]!='S'){
                vec.push_back(id(i,j)); 
                if(aa[i-1][j]!=0&&aa[i-1][j]!='S') nnum++,add(id(i,j),id(i-1,j),nnum),add(id(i-1,j),id(i,j),nnum);
                if(aa[i][j-1]!=0&&aa[i][j-1]!='S') nnum++,add(id(i,j),id(i,j-1),nnum),add(id(i,j-1),id(i,j),nnum);  
            }
            if(aa[i][j]=='M') M=id(i,j);
            if(aa[i][j]=='P') P=id(i,j);
            if(aa[i][j]=='K') K=id(i,j); 
        }
    fd();
    for(int i=0;i<vec.size();i++)
        if(!vv[vec[i]])
            rt=vec[i],dfs(vec[i],0);
    bfs(); 
    if(ans==-1) printf("NO");
    else printf("%d",ans);
    return 0;
}