【poi9911】Store-Keeper wdata(BFS+点双连通分量)

【poi9911】Store-Keeper wdata

Time Limit:10000MS  Memory Limit:256000K
Total Submit:1 Accepted:1 
Case Time Limit:1000MS

Description

有一个仓库被分成n*m 个矩形区域,如果两个区域有一条公共边,则被认为这两个区域相邻。包裹都放在一个区域中,剩余的区域或者空闲或者被集装箱占有,这是因为集装箱太重,仓库管理员不能将集装箱搬走。仓库管理员目是是要将包裹从开始的P区域移动到最后的K区域。他可以从空区域走到与之相邻的一个空区域。当仓库管理员走到与包裹相邻的区域时,它可以推动包裹,具体的推动方法如下所示: 
 
任务: 
写一个程序: 
从MAG.IN 文本文件中读入一个储藏表,开始位置为仓库管理员,最后位置为包裹移动的位置 
计算包裹从开始位置可能移动到目标位置的最少移动次数 . 
将结果输出到文本文件 MAG.OUT中. 

Input

在文件MAG.IN 的第一行有两个用单个空格分隔的正整数n,m<=100. 接下来是货物存放二维表.共N行,每行为M 个字母组成的单词,字母分别是S, M, P, K, w. 第i单词的第j个位置表示第i行第j列区域的信息,可能是如下内容: 
S – 集装箱, 
M –仓库管理员的位置, 
P –包裹开始的位置, 
K –包裹最后的位置, 
w –空区域. 
S, M, P 和 K 在文件MAG.IN 中只出现一次. 

Output

你的结果写入到文件MAG.OUT中: 
如果包裹不能移动到目的位置,则写入NIE。 
如果包裹能移动到目的位置,则写入最小的移动次数 

Sample Input

10 12
SSSSSSSSSSSS
SwwwwwwwSSSS
SwSSSSwwSSSS
SwSSSSwwSKSS
SwSSSSwwSwSS
SwwwwwPwwwww
SSSSSSSwSwSw
SSSSSSMwSwww
SSSSSSSSSSSS
SSSSSSSSSSSS

Sample Output

7

Source

题目:http://61.180.148.52:1000/JudgeOnline/showproblem?problem_id=1401

分析:转BYVoid

============================================================================

这道题描述的是我们玩过的经典的小游戏,推箱子。由于要求步数最少,基本的想法是BFS,记录人的位置和箱子的位置两个状态。但是状态数过多,有100*100*100*100,进一步思考可以发现记录人的绝对位置是没有必要的,因为只有人在箱子旁边的单元格内,才能推箱子,所以只需记录箱子的位置和人在箱子的方向,只有100*100*4个状态。

BFS过程中,状态扩展分类两种情况,一种是向前推箱子,步数要加1,另一种是改变人的方向。推箱子只需判断前面的位置是否是空地,如果是障碍则不能继续扩展。改变人的方向就有些复杂了,假设箱子的位置是(x0,y0),人原来的位置是(x1,y1),新的位置是(x2,y2),则要判断(x1,y1)到(x2,y2)是否存在不经过(x0,y0)的路径。简单的想法是floodfill,时间复杂度为O(NM)。注意状态判重,还有就是初始位置的确定。由于人初始不一定在箱子边,需要floodfill一下,求出人能到达的箱子旁边的位置,如果根本不能到达,显然无解。

按这种想法写出的程序是要超时的,加上些例如我们在玩推箱子的时候不能把箱子推到墙角之类无关紧要的优化,还是无法通过。思考算法的主要瓶颈就在于判断改变方向时每次都要floodfill一遍,如果能把这里优化一下,或许就能通过。

进一步思考,发现两地如果连通,必须至少存在一条路径,如果总是连通,必须至少存在两条路径。也就是说,无论箱子作为障碍挡在哪条路径上,总还有另一条路径使这两点连通。这恰好是图论中双连通分支(点双连通)的定义,即没有割点的连通分支。所以可以这样判断,对于一个连通图,如果箱子不在割点上,箱子旁边的两点(人的位置)一定连通,如果箱子在割点上,则人的两点位置是否连通,取决于两点是否同属一个双连通分支。于是我们可以预处理出图中的所有割点和双连通分支,然后每次判断两点连通只需O(1)的时间。这样就可以解决这道题了。

==============================================================================

做完这题了解了许多双连通相关的知识,虽然耗掉一个下午和一个晚上,感觉还是值得的,至少1A了,呵呵

代码:

#include<cstdio>
#define min(a,b) (a<b?a:b)
using namespace std;
const int nn=111;
const int oo=1000000000;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
int dfn[nn][nn],low[nn][nn],id[nn][nn],f[nn][nn][4];
int q[nn*nn][3];
bool at[nn][nn],ged[nn][nn];
char map[nn][nn];
int i,j,k,n,m,tsp,cnt,top;
void dfs(int ux,int uy,int qx,int qy)
{
    dfn[ux][uy]=low[ux][uy]=++tsp;
    q[top][0]=ux,q[top][1]=uy,++top;
    for(int i=0,vx,vy;i<4;++i)
        if(map[vx=ux+dx[i]][vy=uy+dy[i]]!='S'&&(vx!=qx||vy!=qy)&&dfn[vx][vy]<dfn[ux][uy])
        if(!dfn[vx][vy])
        {
            dfs(vx,vy,ux,uy);
            low[ux][uy]=min(low[ux][uy],low[vx][vy]);
            if(dfn[ux][uy]<=low[vx][vy])
            {
                id[ux][uy]=++cnt,ged[ux][uy]=1;
                while(q[top-1][0]!=ux||q[top-1][1]!=uy)
                    --top,id[q[top][0]][q[top][1]]=cnt;
            }
        }
        else low[ux][uy]=min(low[ux][uy],low[vx][vy]);
}
void tarjan()
{
    int i,j;
    for(tsp=top=cnt=0,i=1;i<=n;++i)
        for(j=1;j<=m;++j)id[i][j]=ged[i][j]=0,dfn[i][j]=map[i][j]!='S'?0:-1;
    for(i=1;i<=n;++i)
        for(j=1;j<=m;++j)
            if(!dfn[i][j])dfs(i,j,0,0);
}
void find(int &x,int &y,char s)
{
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(map[i][j]==s)
            {
                x=i,y=j;
                return;
            }
}
bool allright()
{
    int i,j,k,x,y,l,r=1;
    find(q[0][0],q[0][1],'M');
    for(l=0;l<r;++l)
        for(i=0;i<4;++i)
            if(map[x=q[l][0]+dx[i]][y=q[l][1]+dy[i]]!='S'&&map[x][y]!='P'&&!at[x][y])
                at[x][y]=1,q[r][0]=x,q[r][1]=y,++r;
    find(i,j,'P');
    for(k=0;k<4;++k)
        if(at[x=i+dx[k]][y=j+dy[k]])return 1;
    return 0;
}
inline bool ok(int x,int y,int k0,int k)
{
    if(k0==k)return 1;
    if(map[x+dx[k]][y+dy[k]]!='S')
    {
        if(ged[x][y])return id[x+dx[k0]][y+dy[k0]]==id[x+dx[k]][y+dy[k]];
        return 1;
    }
    return 0;
}
void BFS()
{
    int i,k,tmp,x,y,l,r=0,x0,y0,k0;
    for(x=1;x<=n;++x)
        for(y=1;y<=m;++y)
            for(k=0;k<4;++k)f[x][y][k]=oo;
    find(x,y,'P');
    for(k=0;k<4;++k)
        if(at[x+dx[k]][y+dy[k]])
            q[r][0]=x,q[r][1]=y,q[r][2]=k,++r,f[x][y][k]=0;
    for(l=0;l<r;++l)
        for(i=0;i<4;++i)
            if(ok(x0=q[l][0],y0=q[l][1],k0=q[l][2],k=(i+2)%4))
                if(map[x=x0+dx[i]][y=y0+dy[i]]!='S'&&f[x][y][k]>(tmp=f[x0][y0][k0]+1))
                    f[x][y][k]=tmp,q[r][0]=x,q[r][1]=y,q[r][2]=k,++r;
    tmp=oo;
    find(x,y,'K');
    for(k=0;k<4;++k)tmp=min(tmp,f[x][y][k]);
    if(tmp==oo)printf("NIE\n");
    else printf("%d\n",tmp);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=-1)
    {
        for(i=0;i<n+2;++i)
            for(j=0;j<m+2;++j)map[i][j]='S',at[i][j]=0;
        for(i=1;i<=n;++i)
            for(getchar(),j=1;j<=m;++j)
                map[i][j]=getchar();
        if(allright())tarjan(),BFS();
        else printf("NIE\n");
    }
    return 0;
}


你可能感兴趣的:(【poi9911】Store-Keeper wdata(BFS+点双连通分量))