题目传送门

暴力搜索

看到这道题的第一反应就是直接上$bfs$啦,也没有想到什么更加优秀的算法。

然后就是$15$分钟打了$70$分,有点震惊,纯暴力诶,这么多白给分嘛,太划算了,这可是$D2T3$诶。

 #include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
#include<map>
#include<iostream>
#include<cmath>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define N 35
int rd()
{
int f=,s=;char c=getchar();
while(c<''||c>''){if(c=='-') f=-;c=getchar();}
while(c>=''&&c<=''){s=(s<<)+(s<<)+(c^);c=getchar();}
return f*s;
}
struct node{
int mx,my,nx,ny;//空格的位置,初始棋子现在的位置
int stp;//步数
};
int n,m,q;
int mp[N][N];
int ex,ey,sx,sy,tx,ty;
queue<node>Q;
const int dx[]={,-,,},dy[]={,,,-};
bool vis[N][N][N][N];
bool check(int xx,int yy)
{
if(xx<||xx>n||yy<||yy>m||mp[xx][yy]==) return ;
return ;
}
int main()
{
n=rd(),m=rd(),q=rd();
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
mp[i][j]=rd();
while(q--)
{
ex=rd(),ey=rd(),sx=rd(),sy=rd(),tx=rd(),ty=rd();
if(sx==tx&&sy==ty)
{//下面村答案是在拓展节点之后存 所以这里要特判
puts("");//如果取出时存答案就不用特判(好像也不用break很多层了
continue;
}
memset(vis,,sizeof(vis));
bool flag=;
while(!Q.empty()) Q.pop();
node s;s.mx=ex,s.my=ey,s.nx=sx,s.ny=sy,s.stp=;
vis[ex][ey][sx][sy]=;
Q.push(s);
while(!Q.empty())
{
s=Q.front();Q.pop();
for(int i=;i<;i++)
{
node nxt;
nxt.mx=s.mx+dx[i],nxt.my=s.my+dy[i];
if(!check(nxt.mx,nxt.my)) continue;
if(nxt.mx==s.nx&&nxt.my==s.ny) nxt.nx=s.mx,nxt.ny=s.my;
else nxt.nx=s.nx,nxt.ny=s.ny;
nxt.stp=s.stp+;
if(nxt.nx==tx&&nxt.ny==ty)
{
flag=;
printf("%d\n",nxt.stp);
break;
}
if(vis[nxt.mx][nxt.my][nxt.nx][nxt.ny]) continue;
Q.push(nxt);
vis[nxt.mx][nxt.my][nxt.nx][nxt.ny]=;
}
if(flag) break;
}
if(flag) continue;
else puts("-1");
}
return ;
}

Code

正解分析

纯暴力的$bfs$会超时,是因为搜索了很多无用状态:真正有用的状态其实是起点格子走的状态,但是这道题用搜索方便转移的却是空白格子,因为题目的规则是空白格子可以移动(相当于和旁边的格子交换位置)。

只有起点格子在空白格子四周起点格子才能动,所以应该是尽量让空白格子往起点格子周围跑,而不是毫无目的地乱跑,空白格子在乱跑的时候实际上拓展了很多无用节点。

那么,就先用一个$bfs$预处理出空白格子到达起点格子四周的距离,然后再从四周分别开始走,取最小的。


从四周开始走的时候还是不考虑暴搜(会产生多余状态),我们只管有用的状态。还是这个道理:起点格子是要依托于空白格子才能够移动的,所以状态要保持空白格子一直在起点格子四周。

那么定义状态$(i,j,k)$表示起点格子的坐标为$(i,j)$,空白格子在它的$k$方向(也即是坐标为$(i+dx[k],j+dy[k])$)。

考虑转移到后继状态:

1.可以是起点格子通过空白格子进行移动(就是俩格子交换位置)

2.可以是空白格子在起点格子四周移动,可以看成转方向。如果只用上面那种转移,也就是用起点格子自己换方向,可能没那么优秀,因为换来换去要绕一绕 。而且起点格子有可能会跑到一个离终点的地方去了,更不优。而只动空白格子可能会优秀一些 因为空白格子最优(到处都是活动格子)只用走2~4步。


然后我们发现,对于多组数据,棋盘的形状是没有改变的,所以大概可以预处理一下然后再询问。

怎么预处理呢?有一个比较实用的技巧,就是把状态当成图的节点,状态之间转移的代价当做边权,然后求从一个状态到另一个状态的最小步数就是从一个点到另一个点的最短路啦(其实本来$bfs$也和最短路有关系(强行沾边))

所以只需要处理出一些可行状态(可以将他们编号,以便进行最短路),然后用上面的两种转移关系连边:对于第一种,边权就是$1$,对于第二种,边权就是两点之间的最短路(可以$bfs$预处理,此时的最短路相当于边权为$1$)。由于边权不是全为$1$,不能用$bfs$,要写最短路算法,只要不是$floyd$,随便写哪个都可以的。

每次询问的时候,求出空白格子不经过起点格子(如果经过,起点格子位置就变啦)到起点格子四周的最短距离(可$bfs$),再从那四个状态出发(可以同时压进去的,因为答案没有叠加部分,都是一直不停更新最短距离,相当于取$min$),跑到终点格子。终点格子的四个状态都可以,取$min$就是答案。


果然吧,还是暴力好写,正解调了一个下午,考试的时候写暴力真的太划算了。

代码示例

 #include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
#include<map>
#include<iostream>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define N 35
int rd()
{
int f=,s=;char c=getchar();
while(c<''||c>''){if(c=='-') f=-;c=getchar();}
while(c>=''&&c<=''){s=(s<<)+(s<<)+(c^);c=getchar();}
return f*s;
}
const int dx[]={,-,,},dy[]={,,,-};
//下上右左
int n,m,q;
int mp[N][N];
int ex,ey,sx,sy,tx,ty;
int cnt[N][N][];
bool check(int xx,int yy)
{
if(xx<=||xx>n||yy<=||yy>m||mp[xx][yy]==) return ;
return ;
}
int tot;
struct node{
int x,y,stp/*广搜的时候用 存步数*/;
};
bool vis[N][N],mark[N*N]/*spfa的标记数组*/;
vector<pair<int,int> >G[N*N];
void add(int u,int v,int w)
{
G[u].push_back(make_pair(v,w));
}
int bfs(int ax,int ay,int bx,int by,int cx,int cy)
{//a到b不经过点c的最短距离
if(ax==bx&&ay==by) return ;//起点就是终点
memset(vis,,sizeof(vis));
queue<node>Q;
while(!Q.empty()) Q.pop();
node s;s.x=ax,s.y=ay,s.stp=;
Q.push(s);
vis[ax][ay]=;
while(!Q.empty())
{
node now=Q.front();Q.pop();
if(now.x==bx&&now.y==by) return now.stp;
for(int i=;i<;i++)
{
node nxt;
nxt.x=now.x+dx[i],nxt.y=now.y+dy[i];
if(!check(nxt.x,nxt.y)) continue;
if(nxt.x==cx&&nxt.y==cy) continue;
if(vis[nxt.x][nxt.y]) continue;
nxt.stp=now.stp+;
Q.push(nxt);
vis[nxt.x][nxt.y]=;
}
}
return INF;//不能到达
}
void Init()
{
//cnt存状态标号 spfa的时候是把点看成状态 用边来表示状态的转移
//[i][j][k]是表示开始的那个点的坐标是(i,j) 空格在它的k方向
tot=;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
for(int k=;k<;k++)
if(mp[i][j]&&check(i+dx[k],j+dy[k]))
cnt[i][j][k]=++tot;//给状态标号 没有标号的就是不可行的状态
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
for(int k=;k<;k++)
if(cnt[i][j][k])
{
int tmp;
if(k==) tmp=;
if(k==) tmp=;
if(k==) tmp=;
if(k==) tmp=;
add(cnt[i][j][k],cnt[i+dx[k]][j+dy[k]][tmp],);
//连单向边就可以了 后面会遍历到旁边状态连回来的
//交换空白格子和起点格子 只需要一步
}
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
for(int k=;k<;k++)
for(int p=;p<;p++)//枚举起点格子相邻一圈的两个格子
if(k!=p&&cnt[i][j][k]&&cnt[i][j][p])
add(cnt[i][j][k],cnt[i][j][p],bfs(i+dx[k],j+dy[k],i+dx[p],j+dy[p],i,j));
//可以起点格子不动 而空白格子绕着它转(换方向)
//如果用起点格子自己换方向 可能没那么优秀因为要绕一绕
//而且起点格子有可能会跑到一个离终点的地方去了 更不优
//而只动空白格子可能会优秀一些 因为空白格子最优(到处都是活动格子)只用走2~4步 //经过其他格子没有任何影响 因为都是1 只是空白格子变了而已
//而经过起点格子则会改变起点格子的坐标
}
int dis[N*N];
int spfa()
{
queue<int>Q;
while(!Q.empty()) Q.pop();
if(sx==tx&&sy==ty) return ;
memset(dis,INF,sizeof(dis));
memset(mark,,sizeof(mark));
for(int k=;k<;k++)
{//空格先走到起点的四周 初始状态 以空格从起点四周开始
if(cnt[sx][sy][k])
{
dis[cnt[sx][sy][k]]=bfs(ex,ey,sx+dx[k],sy+dy[k],sx,sy);
Q.push(cnt[sx][sy][k]);
mark[cnt[sx][sy][k]]=;
}
}
while(!Q.empty())
{
int u=Q.front();Q.pop();
mark[u]=;
for(int i=;i<G[u].size();i++)
{
int v=G[u][i].first,w=G[u][i].second;
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
if(!mark[v])
{
Q.push(v);
mark[v]=;
}
}
}
}
//空格在哪里不用管 起点格子到了就可以
//从空格在起点格子四周随便哪个地方的状态到空格在终点格子四周随便哪个地方的状态
int res=INF;
for(int k=;k<;k++)
if(cnt[tx][ty][k])
res=min(res,dis[cnt[tx][ty][k]]);
if(res==INF) return -;//走不到
return res;
}
int main()
{
n=rd(),m=rd(),q=rd();
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
mp[i][j]=rd();
Init();
while(q--)
{
ex=rd(),ey=rd(),sx=rd(),sy=rd(),tx=rd(),ty=rd();
printf("%d\n",spfa());
}
return ;
}

Code

NOIp2013D2T3 华容道【搜索&图论-最短路】的更多相关文章

  1. Luogu1979 NOIP2013D2T3 华容道 搜索、最短路

    题目传送门 题意:给出一个$N \times M$的棋盘,棋盘上有一些块可以移动,有一些块无法移动.$Q$次询问,每一次询问给出三个块$a,b,c$,将$a$块变为空格,空格旁边可移动的块可以与空格交 ...

  2. NOIP模拟赛 华容道 (搜索和最短路)蒟蒻的第一道紫题

    题目描述 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间. 小 B 玩的华容道 ...

  3. 图论&搜索:K短路-启发式搜索

    判断第k短路的权值是否小于T 直接把队友的代码拿过来了,一定很经典 #include <iostream> #include <queue> #include <cstr ...

  4. HDU 5521 [图论][最短路][建图灵感]

    /* 思前想后 还是决定坚持写博客吧... 题意: n个点,m个集合.每个集合里边的点是联通的且任意两点之间有一条dis[i]的边(每个集合一个dis[i]) 求同时从第1个点和第n个点出发的两个人相 ...

  5. 图论(最短路&最小生成树)

    图论 图的定义与概念 图的分类 图,根据点数和边数可分为三种:完全图,稠密图与稀疏图. 完全图,即\(m=n^2\)的图\((m\)为边数,\(n\)为点数\()\).如: 1 1 0 1 2 1 1 ...

  6. D1图论最短路专题

    第一题:poj3660 其实是Floyed算法的拓展:Floyd-Wareshall.初始时,若两头牛关系确定则fij = 1. 对于一头牛若确定的关系=n-1,这说明这头牛的排名是确定的. 通过寻找 ...

  7. 图论最短路——spfa

    今天开始图论的最短路的最后复习,今天自己手打spfa虽然看了一眼书,但是也算是自己打出来的,毕竟很久没打了,而且还是一遍a代码下来15min左右就搞完了,成就感++.所以呢来篇博客记录一下. 香甜的黄 ...

  8. 2018/7/16 YMOI模拟 NOIP2013D2T3华容道

    题目描述 Description 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面,华容道是否根本就无法完成,如果能完成,最少需要多少时间. ...

  9. 【bzoj1415】【聪聪和可可】期望dp(记忆化搜索)+最短路

    [pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=57148470 Descrition 首先很明显是 ...

随机推荐

  1. redis配置主从备份以及主备切换方案配置(转)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/gsying1474/article/de ...

  2. TensorFlow2.0提示Cannot find reference 'keras' in __init__.py

    使用TensorFlow2.0导入from tensorflow.keras import layers会出现Cannot find reference 'keras' in __init__.py提 ...

  3. 利用msyqlfont + plsql 客户端 完成msyql数据向oracle的转移

    方法一: 1.这是mysqlfont 连接工具 ,选中表右键点击 输出->csv文件 2.选择导出的文件为ANSI型,因为csv文件excel打开的默认编码方式为ANSI这样可以防止中文在exc ...

  4. JavaWeb-SpringSecurity初认识

    Spring Security 安全 百度百科 功能:Spring Security对Web安全性的支持大量地依赖于Servlet过滤器.这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安 ...

  5. 微信小程序_(组件)scroll-view可滚动视图

    微信小程序scroll-view组件官方文档 传送门 提前准备:使用<view>组件制作五条撑满的横向区域 <!--index.wxml--> Cynical丶Gary < ...

  6. springMVC中的ModelAndView说明

    ModelAndView 类别就如其名称所示,是代表了Spring Web MVC程式中呈现画面时所使用Model资料物件与View资料物件,由于Java程式中一次只能返回一个物件,所以ModelAn ...

  7. 10.矩形覆盖 Java

    题目描述 我们可以用2**1的小矩形横着或者竖着去覆盖更大的矩形.请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法? 思路 其实,倒数第一列要么就是1个2**1的矩形竖着放,要么就 ...

  8. Leetcode题目34.在排序数组中查找元素的第一个和最后一个位置(中等)

    题目描述: 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置. 你的算法时间复杂度必须是 O(log n) 级别. 如果数组中不存在目标 ...

  9. 石川es6课程---4、箭头函数

    石川es6课程---4.箭头函数 一.总结 一句话总结: 相当于函数的简写,类似python lambda 函数,先了解即可 let show1 = function () { console.log ...

  10. 【java多线程】多线程中的long和double

    在看一些代码的时候,会发现在定义long型和double型的变量时,会在前面加上volatile关键字,当然也会看到在其它原子类型的变量前加上这个关键字,但这里要说的还是有区别的. 在java中,ja ...