zoukankan      html  css  js  c++  java
  • 【P1979】 NOIP2013 华容道

    我真的菜,调了整整一天,最后还是没调出来心态炸了不想调了。 果然紫题不去看题解还是做不出来啊,但是看题解就在一定程度上失去训练的意义了啊。

    调整一下心态,写下这篇博客

    (Description)

    题面
    给你一个(n*m)的华容道棋盘,其中有标有(0)的部分不能移动,其他格子都标为(1),在标有(1)的格子中有一个格子是空格的(华容道的规则),有(Q)组询问,每组询问六个数表示空白格子的坐标和起始格子的坐标以及目标点的坐标,要求求出最小步数,使得起始格子移动到目标位置,如果无解输出(-1)
    如果不明白可以看看上面链接里的样例解释

    (Solution)

    首先华容道问题有一个转换,因为一开始起始格子不一定能移动,但实际我们消耗了步数,因为我们移动了其他格子(废话)。
    我们发现如果移动一个格子就必须靠近空白格子才能移动(又是废话),所以我们每走一步空白格子一定发生位移,所以从游戏开始到游戏结束的过程空白格子走的步数就是答案
    显然我们可以(BFS),让空白格子随便走,记录下空白格子和指定(就是一开始指定的那个)格子的位置,假如指定格子到达目标位置,这时候一定是最小步数,直接返回即可。
    但是我们直接这么做会发现指定格子没法移动,所以我们判断一下如果空白格子移动到指定格子上就交换他俩的位置即可。
    这样做需要注意几点:
    (1.)显然华容道游戏的空白格子可能重复走同一个位置,因为我们设计的状态不仅只有空白格子,还有指定格子,所以(vis)数组不能只记录空白格子访问情况,要开四维记录,这样才能考虑所有情况。
    注意在判断是否交换位置之后再判断(vis)数组。
    总之,(BFS)过程中(vis)数组需要记录所有不同的状态
    (2.)定义全局变量一定要小心啊。
    下面是暴力(code)

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<algorithm>
    #define re register
    #define maxn 32
    using namespace std;
    inline int read()
    {
    	int x=0,f=1; char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int dx[5]={0,-1,0,1};
    int dy[5]={-1,0,1,0};
    int n,m,Q,ex,ey,sx,sy,tx,ty;
    int vis[maxn][maxn][maxn][maxn],a[maxn][maxn],ans,tmp1,tmp2,tmp3;
    struct node{
    	int nowx,nowy,m_nowx,m_nowy,cnt;
    };
    queue<node> q;
    void bfs()
    {
    	while(!q.empty()) q.pop();
    	memset(vis,0,sizeof(vis));
    	q.push((node){ex,ey,sx,sy,0});
    	vis[ex][ey][sx][sy]=1;
    	while(!q.empty())
    	{
    		node top=q.front();
    		q.pop();
    		if(top.m_nowx==tx&&top.m_nowy==ty)
    		{
    			ans=top.cnt;
    			break;
    		}
    		for(int i=0;i<4;++i)
    		{
    			int nxtx=top.nowx+dx[i];
    			int nxty=top.nowy+dy[i];
    			int nxtmx=top.m_nowx;
    			int nxtmy=top.m_nowy;
    			if(nxtx<1||nxtx>n||nxty<1||nxty>m) continue;
    			if(a[nxtx][nxty]==0) continue;
    			if(nxtx==top.m_nowx&&nxty==top.m_nowy)
    			{
    				if(vis[nxtx][nxty][top.nowx][top.nowy]) continue;
    				nxtmx=top.nowx;
    				nxtmy=top.nowy;
    			}
    			if(vis[nxtx][nxty][nxtmx][nxtmy]) continue;
    			vis[nxtx][nxty][nxtmx][nxtmy]=1;
    			q.push((node){nxtx,nxty,nxtmx,nxtmy,top.cnt+1});
    		}
    	}
    }
    int main()
    {
    	n=read(),m=read(),Q=read();
    	for(re int i=1;i<=n;++i)
    	 for(re int j=1;j<=m;++j)
    	   a[i][j]=read();
    	for(re int i=1;i<=Q;++i) 
    	{
    		ex=read(),ey=read(),sx=read(),sy=read(),tx=read(),ty=read();
    		ans=-1;
    		bfs();
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    然后你就会发现一道紫题暴力竟然有(70pts),它的复杂度是(O(q(nm)^2)),因为每个点有两种状态很显然。要是我考试遇到这道题绝对敲个暴力就走人了,正解估计还要码一个小时还要调试,多麻烦。
    但是我们本着精益求精的态度,去探究(100pts)做法

    首先我码了个(A*),结果变成了(65pts),看来(A*)对这个题优化不大,反而带来了(log)的复杂度。
    下面是(A*)代码

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define re register
    #define maxn 32
    using namespace std;
    inline int read()
    {
    	int x=0,f=1; char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int dx[5]={0,-1,0,1};
    int dy[5]={-1,0,1,0};
    int n,m,Q,ex,ey,sx,sy,tx,ty;
    int vis[maxn][maxn][maxn][maxn],a[maxn][maxn],ans,tmp1,tmp2,tmp3;
    struct node{
    	int nowx,nowy,m_nowx,m_nowy,cnt,G;
    	bool operator <(const node&rhs) const{
    		return rhs.cnt+rhs.G<cnt+G;
    	}
    };
    priority_queue<node> q;
    void bfs()
    {
    	while(!q.empty()) q.pop();
    	memset(vis,0,sizeof(vis));
    	q.push((node){ex,ey,sx,sy,0,abs(sx-tx)+abs(sy-ty)});
    	vis[ex][ey][sx][sy]=1;
    	while(!q.empty())
    	{
    		node top=q.top();
    		q.pop();
    		if(top.m_nowx==tx&&top.m_nowy==ty)
    		{
    			ans=top.cnt;
    			break;
    		}
    		for(int i=0;i<4;++i)
    		{
    			int nxtx=top.nowx+dx[i];
    			int nxty=top.nowy+dy[i];
    			int nxtmx=top.m_nowx;
    			int nxtmy=top.m_nowy;
    			if(nxtx<1||nxtx>n||nxty<1||nxty>m) continue;
    			if(a[nxtx][nxty]==0) continue;
    			if(nxtx==top.m_nowx&&nxty==top.m_nowy)
    			{
    				if(vis[nxtx][nxty][top.nowx][top.nowy]) continue;
    				nxtmx=top.nowx;
    				nxtmy=top.nowy;
    			}
    			if(vis[nxtx][nxty][nxtmx][nxtmy]) continue;
    			vis[nxtx][nxty][nxtmx][nxtmy]=1;
    			q.push((node){nxtx,nxty,nxtmx,nxtmy,top.cnt+1,abs(nxtmx-tx)+abs(nxtmy-ty)});
    		}
    	}
    }
    int main()
    {
    	n=read(),m=read(),Q=read();
    	for(re int i=1;i<=n;++i)
    	 for(re int j=1;j<=m;++j)
    	   a[i][j]=read();
    	for(re int i=1;i<=Q;++i) 
    	{
    		ex=read(),ey=read(),sx=read(),sy=read(),tx=read(),ty=read();
    		ans=-1;
    		bfs();
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    终于开始考虑正解

    当我们尝试几次后发现,指定格子移动需要空白格子在旁边。所以整个过程就是空白格子接近指定格子,然后指定格子依靠空白格子一步步往目标位置走,而指定格子走一步可能需要空白格子走好几步。
    所以统计答案从空白格子一步步走变成了指定格子走到目标位置,而空白格子不在指定格子旁边的状态是无效的,我们只需要记录他在指定格子上下左右的情况即可。
    假设指定格子当前在(i,j),空白格子在它的上下左右,那么我们可以给状态(i,j,d)编号((d)表示上下左右(4)个取值),所以我们的状态数从((nm)^2-->4nm)大大简化。
    考虑怎么在状态之间转移,显然空白格子有两种方式走,第一种是和指定格子交换让指定格子朝这个方向移动,第二种是空白格子通过一系列路径到达指定格子的另一个方向让下一步指定格子可以朝其他方向移动。
    于是我们可以通过下面的(BFS),对于每一个点的四种状态都进行(BFS),然后对于两种转移方式连边,边权为步数。

    inline int get_id(int x,int y,int d) {return x*120+y*4+d;}
    void bfs2(int fx,int fy,int ox,int oy,int d)
    {
    //fx,fy表示空白格子位置,ox,oy表示指定格子位置
    	while(!q.empty()) q.pop();
    	int ID=get_id(ox,oy,d);
    	memset(dis,-1,sizeof(dis));
    	dis[ex][ey]=0;
    	dis[ox][oy]=1;
    	q.push((state){fx,fy});
    	
    	while(!q.empty())
    	{
    		state n=q.front();
    		int nx=n.x,ny=n.y;
    		q.pop();
    		for(re int k=0;k<4;++k)
    		{
    			int kx=nx+dx[k],ky=ny+dy[k];
    			if(kx>N||kx<1||ky>m||ky<1) continue;
    			if(a[kx][ky]&&dis[kx][ky]==-1&&(kx!=ox||ky!=oy))
    			 dis[kx][ky]=dis[nx][ny]+1,q.push((state){kx,ky});
    		}
    	}
    	if(d>3) return;//这里是为了后面用另一种BFS
    	for(int k=0;k<4;++k)
    	{
    		int kx=ox+dx[k],ky=oy+dy[k];
    		if(kx>N||kx<1||ky>m||ky<1) continue;
    		if(a[kx][ky]&&dis[kx][ky]!=-1&&(kx!=fx||ky!=fy))
    		 add(ID,get_id(ox,oy,k),dis[kx][ky]);//第二种转移方式
    	}
    	int ID2=get_id(fx,fy,d)^1;
    	add(ID,ID2,1);//交换只需要一步
    }
    

    所以当空白格子靠近指定格子后把指定格子往回“带”的过程都可以用上面的转移表示,因此用一个(SPFA)跑最短路,对于最终状态(diss[get_id(tx,ty,d)])四种(d)(min)就是答案。
    那么有一个问题是我们空白格子一开始不一定在指定格子周围,需要走最短距离到指定格子四周,所以我们用一个(BFS)求从空白格子的起始位置到达指定格子的四个位置的最短距离,作为(spfa)四个起点的(diss)值即可,最终求出答案就是所求答案。
    注意再求空白格子起始位置到指定格子距离时到达指定格子就停下,因为这个过程默认指定格子不移动,如果穿过指定格子会有不合法的情况
    下面是满分做法

    (Final code)

    #include<queue> 
    #include<cstdio> 
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=50,inf=0x3f3f3f3f;
    struct a{int x,y;}qs[400000]; 
    int mov[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
    int mapp[N][N],node[N][N][4],inq[4*N*N];
    int temp[N][N],dis[N][N][4][4],diss[4*N*N];
    int p[4*N*N],noww[16*N*N],goal[16*N*N],val[16*N*N];
    int n,m,q,f,b,cnt,num,t1,t2,t3,t4,t5,t6,s,t,tep,p1,p2;
    queue<int> qx; 
    bool ok(int xx,int yy)
    {
        return xx>=1&&xx<=n&&yy>=1&&yy<=m&&mapp[xx][yy];
    }
    void link(int f,int t,int v)
    {
        noww[++cnt]=p[f],p[f]=cnt;
        goal[cnt]=t,val[cnt]=v;
    }
    int GET_DIS(int sx,int sy,int ex,int ey)
    {
        if(sx==ex&&sy==ey) return 0;
        memset(temp,-1,sizeof temp);
        temp[sx][sy]=0,qs[f=b=0]=(a){sx,sy};
        while(f<=b)
        {
            int tx=qs[f].x,ty=qs[f].y;
            for(int i=0;i<4;i++)
            {
                int nx=tx+mov[i][0],ny=ty+mov[i][1];
                if(ok(nx,ny)&&temp[nx][ny]==-1)
                {
                    temp[nx][ny]=temp[tx][ty]+1; qs[++b]=(a){nx,ny};
                    if(nx==ex&&ny==ey) return temp[nx][ny];
                }
            }
            f++;
        } 
        return inf;
    }
    int main()
    {
        scanf("%d%d%d",&n,&m,&q);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&mapp[i][j]);
                for(int k=0;k<4;k++)
                    node[i][j][k]=++num;
            }
        memset(dis,0x3f,sizeof dis);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(mapp[i][j])
                {
                    mapp[i][j]=0;
                    for(int k=0;k<4;k++)
                        if(ok(t1=i+mov[k][0],t2=j+mov[k][1]))
                            for(int h=0;h<4;h++)
                                if(ok(t3=i+mov[h][0],t4=j+mov[h][1]))
                                    dis[i][j][k][h]=GET_DIS(t1,t2,t3,t4)+1;
                    mapp[i][j]=1;
                }  
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                for(int k=0;k<4;k++)
                    for(int h=0;h<4;h++)
                        if(dis[i][j][k][h]<inf)
                            link(node[i][j][k],node[i+mov[h][0]][j+mov[h][1]][h^1],dis[i][j][k][h]);
        while(q--)
        {
            scanf("%d%d%d%d%d%d",&t1,&t2,&t3,&t4,&t5,&t6);
            if(t3==t5&&t4==t6) {printf("0
    ");continue;} 
            s=++num; t=++num;
            mapp[t3][t4]=0;
            for(int i=0;i<4;i++)
                if(mapp[p1=t3+mov[i][0]][p2=t4+mov[i][1]])
                    if((tep=GET_DIS(t1,t2,p1,p2))<inf) link(s,node[t3][t4][i],tep);
            mapp[t3][t4]=1;
            for(int i=0;i<4;i++)
                if(mapp[t5+mov[i][0]][t6+mov[i][1]]) link(node[t5][t6][i],t,0);
            memset(diss,0x3f,sizeof diss),diss[s]=0; qx.push(s),inq[s]=1; 
            while(!qx.empty())
            {
                int tn=qx.front();
                qx.pop(); inq[tn]=0;
                for(int i=p[tn];i;i=noww[i])
                    if(diss[goal[i]]>diss[tn]+val[i])
                    {
                         diss[goal[i]]=diss[tn]+val[i];
                         if(!inq[goal[i]]) qx.push(goal[i]),inq[goal[i]]=1;
                    }
            }
            diss[t]==inf?printf("-1
    "):printf("%d
    ",diss[t]);
        }
        return 0;
    } 
    
  • 相关阅读:
    Linux内核文档:包含 kernel-doc 注释
    Linux内核文档:如何写符合 kernel-doc 规范的注释
    [记录点滴] 使用工具和命令对redis数据进行备份恢复
    聊聊CMDB的前世今生
    我是如何走上运维岗位的?谈谈新人入职运维发展的注意事项
    如何从生命周期的视角看待应用运维体系建设?
    标准化体系建设(下):如何建立基础架构标准化及服务化体系?
    标准化体系建设(上):如何建立应用标准化体系和模型?
    微服务架构时代,运维体系建设为什么要以“应用”为核心?
    lsattr命令
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/11690428.html
Copyright © 2011-2022 走看看