搞了半天八数码弄不出来就只好来打题解  这道题是在搜索a碰到的(链接: http://pan.baidu.com/s/1jG9rQsQ ) 感觉题目最大亮点就是这英文简写"ni", 真是令人感慨出题老师的才华啊  好了,废话不多说  题目内容如下(闲烦请无视之,下下方有简单的介绍):

【问题描述】
贝 西(Bessie)在卡摩洛遇到了棘手的情况:她必须穿越由Ni骑士把守的森林。 骑士答应Bessie
只要 Bessie 能给他们带来一丛灌木就能安全穿越森林。时间宝贵,Bessie 必须尽快找到一丛灌木
然后给骑士送去。
Bessie有一张森林的地图。森林被划分成了W×H个网格(1≤W≤1000; 1≤H≤1000)。 每 个
网格用0~4的整数表示。

0:可以通行;
1:无法通行(沼泽、悬崖、杀手) ;
2:Bessie的起始位置;
3:骑士的起始位置;
4:灌木丛。
Bessie 只能朝上下左右四个方向行走,每走一个网格要花一天的时间,请超出一条到达骑士的
最短路。问题保证有解。
【输入】
输入文件的第一行是W和H;
一般来讲,后面有 H 行每行 W 个整数(即 0~4),表示地图。但是当某行的整数个数超过 40 个
时,就会另起一行。
【输出】
输出Bessie至少需要几天才能把灌木丛带给骑士。

【输入样例】
8 4
4 1 0 0 0 0 1 0
0 0 0 1 0 1 0 0
0 2 1 1 3 0 4 0
0 0 0 4 1 1 1 0
【输出样例】
11
【Hint】
Explanation of the sample: 

Width=8, height=4. Bessie starts on the third row, only a few squares away from the Knights.
Bessie can move in this pattern to get a shrubbery for the Knights: N, W, N, S, E, E, N, E, E, S, S. She gets the
shrubbery in the northwest corner and then makes her away around the barriers to the east and then south to
the Knights.

  题目内容一大堆,说简单点就是给出一张W*H(1<=W,H<=1000)的地图,每个位置都标有0..4的数字。其中0表示可以通过,1表示不能通过,2表示Bessie的起始位置,3表示骑士起始位置,4表示树丛。现在要求求出一条最短的路,使骑士经过树丛至少一次,且能够到达Bessie所在位置。

  一开始看到题目认为这个好简单啊  不就是一个裸的bfs吗  于是就开始打了第一遍代码  打了之后发现思路很混乱  往哪儿开始搜索都搞不清  于是再看题目  这一看就明白了  就是题中的单精度小女孩(......)要去找它的骑士大哥(666......)当然,这位小女孩还要经过一个树丛柑橘(这不是废话吗)

   这给人第一感觉就是从树丛开始往girl和boy搜素  用一个s数组保存到某个点的最小距离  一开始值设置成0  然后从第一个树开始一个个搜过去  每搜一次保存最小值 也就是s[a1][b1]+s[a2][b2](a1,b1,a2,b2分别是girl和boy的坐标位置   当然这里也需要注意一下如果没有搜到的话  bfs也是会退出的  这样的话相应s数组的值也可能还是0  这里就要加一个特判  也就是当两个s的值都不等于0时才进行  (一开始答案输出老是0  看到这里把数据输出来才知道被坑了   所以说数组返回值有风险(适当的宾语前置。),使用需谨慎。

好像又讲开了,那么我们还是回归正题。(啊,正题是什么......)   好,这样几遍的bfs之后样例就能正确输出了  (好高兴啊)  过程代码如下:

 void bfs(int a,int b)
{
s[a][b]=;
q[].push(a);
q[].push(b);
q[].push();
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
for(int d=;d<=;d++)
{
int x=q[].front()+dx[d];
int y=q[].front()+dy[d];
if(g[x][y]==||x<||x>h||y<||y>w)
continue;
if(s[x][y]==)
{
s[x][y]=q[].front()+;
// cout<<"x "<<x<<' '<<"y "<<y<<endl;
// cout<<"s "<<s[x][y]<<endl;
sum2++;
q[].push(x);
q[].push(y);
q[].push(s[x][y]);
if(s[a1][b1]>&&s[a2][b2]>)
{
// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
return;
}
}
}
q[].pop();
q[].pop();
q[].pop();
}
}

  然后去测  只过了8个点  (很郁闷  这难道不是bfs吗  再看看数据100w 应该能过嘛  )但再看 我这里的不止是一次bfs  而是对于每个草丛都要来一次bfs  复杂度就由此极大的提高了  有没有办法剪枝呢?

  于是我会教室又去想  终于在眼保健操的时候有了一点灵感: 既然问题的一方面出在对于每个树都搜过去而造成了某种意义上的超时  那么我们是不是可以在这里加个条件以减少bfs的次数呢?  如果当前搜的书与起点终点的曼哈顿距离已经比minn大了 那么

无论怎么走都不可能了  和以前做的一道售货员的难题的剪枝像    然后,看到了教室前面开着的电脑  内心想着:反正才第一节嘛  还有时间   于是我就飞奔地上去测了    果然原来6s跑出来的第九个点  这次只要1.3s了   呵呵,然后记得那节晚读整个人都好了

可是  ,1.3s只是较以前的6s有了一些较大的改进  但还远远不够

  那么,再接着想:既然我在bfs的做之前加了这么一个条件   那么   在bfs内部拓展节点的时候是不是也可以试着加个类似的条件呢?  仔细想想bfs的“原理”  它是从近往远的每一排节点进行拓展的 也就是说 下一次取队头出来的肯定是要比这次大的  好  那么在有了理论依据和现实基础后  我果断的在进行是否拓展过的判断之前加了这样一个条件

问题似乎迎刃而解了   再去测  第九个点终于闯入了1s大关  0.7!  再怀着兴奋的心情去搞第十个点时  答案却也再没有显示出来了  (好忧伤......)

  于是我又再想  假如他数据前面几次的minn是很大但又递减的呢   这样的话我在bfs前的判断不久跟没做过一样嘛   WTF!   但转念一想  数据是死的但人是活的嘛   于是我就想干脆对每个树枝对于其道起点终点的曼哈顿距离进行排序  如下图所示:

  然后终于   第十个点在教室0.7s跑出来了!!!!!  (记得我后来高兴了一节历史课)   然后去机房测   0.3       好了,这道题就在机房终于a掉了

  顺便发表一下我的感慨:真的感觉最后把这题做出来很有成就感  每一次改进都是一次小的跃进  oi或许正是需要这种不懈的探索精神吧

  难道故事就这样结束了吗?(Q:有的人认为这句话也可以去掉但也有人说不可以去掉  你认为呢?  A:这句话承上启下 起到了过渡的作用  同时亦吸引了读者的阅读兴趣  (666...))

  好讲到这里我便先把前面的ac代码放出来给大家看看 (由于配置不够  在家测要1s+  请勿见怪  本文最后亦会提及一种更好的方法)

 #include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=+;
struct node
{
int x,y;
}data[maxn];
int g[maxn][maxn],s[maxn][maxn];
int dx[]={,,,-,};
int dy[]={,,,,-};
int w,h,cnt=,sum1=,sum2=;
int a1,b1,a2,b2,k1;
int s1,s2,minn=0x7f7f7f;
queue<int>q[];
int distance1(int a,int b)
{
return (abs(a1-a)+abs(a2-a)+abs(b1-b)+abs(b2-b));
}
void bfs(int a,int b)
{
sum1++;
// queue<int>q[4];
s[a][b]=;
q[].push(a);
q[].push(b);
q[].push();
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
for(int d=;d<=;d++)
{
int x=q[].front()+dx[d];
int y=q[].front()+dy[d];
if(g[x][y]==||x<||x>h||y<||y>w)
continue;
if(distance1(x,y)>minn)
continue;
if(s[x][y]==)
{
s[x][y]=q[].front()+;
// cout<<"x "<<x<<' '<<"y "<<y<<endl;
// cout<<"s "<<s[x][y]<<endl;
sum2++;
q[].push(x);
q[].push(y);
q[].push(s[x][y]);
if(s[a1][b1]>&&s[a2][b2]>)
{
// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
return;
}
}
}
q[].pop();
q[].pop();
q[].pop();
}
}
bool my_comp(node a,node b)
{
return (distance1(a.x,a.y)<distance1(b.x,b.y));
}
int main()
{
ios::sync_with_stdio(false);
// freopen("ni.in","r",stdin);
// freopen("ni.out","w",stdout);
freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
cin>>w>>h;
for(int i=;i<=h;i++)
for(int j=;j<=w;j++)
{
cin>>g[i][j];
if(g[i][j]==)
{
a1=i;
b1=j;
}
if(g[i][j]==)
{
a2=i;
b2=j;
}
if(g[i][j]==)
{
k1++;
data[k1].x=i;
data[k1].y=j;
}
}
// for(int i=1;i<=h;i++)
// {
// for(int j=1;j<=w;j++)
// cout<<g[i][j]<<' ';
// cout<<endl;
// }
// cout<<g[2][1]<<endl;
// cout<<g[2][1]<<endl;
// for(int i=1;i<=h;i++)
// for(int j=1;j<=w;j++)
// if(g[i][j]==4)
sort(data+,data+k1+,my_comp);
for(int i=;i<=k1;i++)
{
if(distance1(data[i].x,data[i].y)>minn)
continue;
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
if(!q[].empty())
q[].pop();
if(!q[].empty())
q[].pop();
if(!q[].empty())
q[].pop();
}
// cout<<i<<' '<<j<<endl;
// while(!q[1].empty())
// q[1].pop();
// while(!q[2].empty())
// q[2].pop();
// while(!q[3].empty())
// q[3].pop(); memset(s,,sizeof(s));
bfs(data[i].x,data[i].y);
// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
if(s[a1][b1]!=&&s[a2][b2]!=&&s[a1][b1]+s[a2][b2]<minn)
minn=s[a1][b1]+s[a2][b2];
}
// cout<<sum1<<' '<<sum2<<endl;
cout<<minn<<endl;
// cout << "\n" << (double)clock() / CLOCKS_PER_SEC;
// cout<<sum<<endl;
return ;
}
   好了,以上是我的代码   做完后我又去问了一下yyl学长    又获得了一些启示:
  1.这道题用手工队列比STL里的要来的快   突出表现在不需要用一个while  一遍遍的弹队列   直接改一下head和tail 就行了
  2.事实上我在改进之后的bfs次数减少了很多(453次bfs,515269次拓展)但事实上并不需要这么多次bfs  只需要从起点和终点开始统计出每个树丛到起点和终点的最少步数  最后对每个树丛统计就好了
  3.山外有山,码外有码     或许换种方式  算法会有更大的改进

  另外 ,至于最后yyl学长提出的改进  我将会尽量在明天给出代码    现在真的太困了   得先睡觉了
 
 

 

  好了 ,起床 ,开更

  对于上面说的方法我刚刚又再敲了一遍  从girl和boy出发 全图遍历至所有的树丛都找到为止 在这里需要一个判断  由于才疏学浅我就直接线性遍历数组了  

  对了  还有一点就是代码中的bfs过程中可以直接引用数组的参数   但我弄了半天都不行(*s ?  s[ ])  于是我就直接重新copy了一遍过程(弱弱的气息......)

 但实际上可以用hash  康托展开等方法来实现之   这些的话等我以后有时间再把代码补上吧   家里电脑(配置很烂,当年机房0.2s的家里跑了3s)勉强1s能过   如右图所示:

代码如下:

 #include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=+;
struct node
{
int x,y;
}data[maxn];
int g[maxn][maxn],s1[][],s2[][];
int dx[]={,,,-,};
int dy[]={,,,,-};
int w,h,cnt=,sum1=,sum2=;
int a1,b1,a2,b2,k1,k2;
int minn=0x7f7f7f;
queue<int>q[];
int distance1(int a,int b)
{
return (abs(a1-a)+abs(a2-a)+abs(b1-b)+abs(b2-b));
}
bool all1()
{
for(int i=;i<=k1;i++)
if(s1[data[i].x][data[i].y]==)
return false;
return true;
}
bool all2()
{
for(int i=;i<=k1;i++)
if(s2[data[i].x][data[i].y]==)
return false;
return true;
}
void bfs1(int a,int b)
{
// sum1++;
// queue<int>q[4];
s1[a][b]=;
q[].push(a);
q[].push(b);
q[].push();
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
for(int d=;d<=;d++)
{
int x=q[].front()+dx[d];
int y=q[].front()+dy[d];
if(g[x][y]==||x<||x>h||y<||y>w)
continue;
// if(distance1(x,y)>minn)
// continue;
if(s1[x][y]==)
{
s1[x][y]=q[].front()+;
// cout<<"x "<<x<<' '<<"y "<<y<<endl;
// cout<<"s "<<s[x][y]<<endl;
// sum2++;
q[].push(x);
q[].push(y);
q[].push(s1[x][y]);
if(all1())
{
// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
return;
}
}
}
q[].pop();
q[].pop();
q[].pop();
}
}
void bfs2(int a,int b)
{
// sum1++;
// queue<int>q[4];
s2[a][b]=;
q[].push(a);
q[].push(b);
q[].push();
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
for(int d=;d<=;d++)
{
int x=q[].front()+dx[d];
int y=q[].front()+dy[d];
if(g[x][y]==||x<||x>h||y<||y>w)
continue;
// if(distance1(x,y)>minn)
// continue;
if(s2[x][y]==)
{
s2[x][y]=q[].front()+;
// cout<<"x "<<x<<' '<<"y "<<y<<endl;
// cout<<"s "<<s[x][y]<<endl;
// sum2++;
q[].push(x);
q[].push(y);
q[].push(s2[x][y]);
if(all2())
{
// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
return;
}
}
}
q[].pop();
q[].pop();
q[].pop();
}
}
bool my_comp(node a,node b)
{
return (distance1(a.x,a.y)<distance1(b.x,b.y));
}
int main()
{
ios::sync_with_stdio(false);
freopen("ni.in","r",stdin);
freopen("ni.out","w",stdout);
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
cin>>w>>h;
for(int i=;i<=h;i++)
for(int j=;j<=w;j++)
{
cin>>g[i][j];
if(g[i][j]==)
{
a1=i;
b1=j;
}
if(g[i][j]==)
{
a2=i;
b2=j;
}
if(g[i][j]==)
{
k1++;
data[k1].x=i;
data[k1].y=j;
}
}
bfs1(a1,b1);
while((!q[].empty())&&(!q[].empty())&&(!q[].empty()))
{
if(!q[].empty())
q[].pop();
if(!q[].empty())
q[].pop();
if(!q[].empty())
q[].pop();
}
bfs2(a2,b2);
for(int i=;i<=k1;i++)
{
if(s1[data[i].x][data[i].y]!=&&s2[data[i].x][data[i].y]!=)
minn=min(minn,s1[data[i].x][data[i].y]+s2[data[i].x][data[i].y]);
}
cout<<minn<<endl;
// for(int i=1;i<=h;i++)
// {
// for(int j=1;j<=w;j++)
// cout<<g[i][j]<<' ';
// cout<<endl;
// }
// cout<<g[2][1]<<endl;
// cout<<g[2][1]<<endl;
// for(int i=1;i<=h;i++)
// for(int j=1;j<=w;j++)
// if(g[i][j]==4)
// sort(data+1,data+k1+1,my_comp);
// for(int i=1;i<=k1;i++)
// {
// if(distance1(data[i].x,data[i].y)>minn)
// continue;
// while((!q[1].empty())&&(!q[2].empty())&&(!q[3].empty()))
// {
// if(!q[1].empty())
// q[1].pop();
// if(!q[2].empty())
// q[2].pop();
// if(!q[3].empty())
// q[3].pop();
// }
//// cout<<i<<' '<<j<<endl;
//// while(!q[1].empty())
//// q[1].pop();
//// while(!q[2].empty())
//// q[2].pop();
//// while(!q[3].empty())
//// q[3].pop();
//
// memset(s,0,sizeof(s));
// bfs(data[i].x,data[i].y);
//// cout<<s[a1][b1]<<' '<<s[a2][b2]<<endl;
// if(s[a1][b1]!=0&&s[a2][b2]!=0&&s[a1][b1]+s[a2][b2]<minn)
// minn=s[a1][b1]+s[a2][b2];
// }
// // cout<<sum1<<' '<<sum2<<endl;
// cout<<minn<<endl;
// cout << "\n" << (double)clock() / CLOCKS_PER_SEC;
// cout<<sum<<endl;
return ;
}

  

算法专题训练 搜索a-T3 Ni骑士(ni)的更多相关文章

  1. Leedcode算法专题训练(搜索)

    BFS 广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点.需要注意的是,遍历过的节点不能再次被遍历. 第一层: 0 -> {6,2,1,5} ...

  2. [算法专题] 深度优先搜索&回溯剪枝

    1. Palindrome Partitioning https://leetcode.com/problems/palindrome-partitioning/ Given a string s, ...

  3. Leedcode算法专题训练(树)

    递归 一棵树要么是空树,要么有两个指针,每个指针指向一棵树.树是一种递归结构,很多树的问题可以使用递归来处理. 1. 树的高度 104. Maximum Depth of Binary Tree (E ...

  4. Leedcode算法专题训练(动态规划)

    递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算. 斐波那契数列 1. 爬楼梯 70. Climbing Stairs (Easy) L ...

  5. Leedcode算法专题训练(分治法)

    归并排序就是一个用分治法的经典例子,这里我用它来举例描述一下上面的步骤: 1.归并排序首先把原问题拆分成2个规模更小的子问题. 2.递归地求解子问题,当子问题规模足够小时,可以一下子解决它.在这个例子 ...

  6. Leedcode算法专题训练(二分查找)

    二分查找实现 非常详细的解释,简单但是细节很重要 https://www.cnblogs.com/kyoner/p/11080078.html 正常实现 Input : [1,2,3,4,5] key ...

  7. Leedcode算法专题训练(排序)

    排序 快速排序 用于求解 Kth Element 问题,也就是第 K 个元素的问题. 可以使用快速排序的 partition() 进行实现.需要先打乱数组,否则最坏情况下时间复杂度为 O(N2). 堆 ...

  8. Leedcode算法专题训练(贪心)

    1. 分配饼干 455. 分发饼干 题目描述:每个孩子都有一个满足度 grid,每个饼干都有一个大小 size,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足.求解最多可以获得满足的孩子数 ...

  9. Leedcode算法专题训练(双指针)

    算法思想 双指针 167. 两数之和 II - 输入有序数组 双指针的典型用法 如果两个指针指向元素的和 sum == target,那么得到要求的结果: 如果 sum > target,移动较 ...

随机推荐

  1. [SAN4N学习笔记]使用SysTick精准延时

    一.准备工作:      将上一节搭建的LED工程复制一份,命名为"2.systick".这一节主要讲如何使用系统的SysTick节拍定时器来进行精准延时程序. 二.程序编写: S ...

  2. Java--创建线程及常用方法

    继承java.lang.Thread类--Thread类代表线程类  它的常用方法如下: static Thread currentThread():返回当前正在运行的线程对象的引用. static ...

  3. xutils Error:(37, 39) 错误: 无法访问HttpRequestBase 找不到org.apache.http.client.methods.HttpRequestBase的类文件

    Android 6.0(api 23)SDK已经强制移除httpclient 解决方案: 1降低api 2build.gradle中添加一句话 android { useLibrary'org.apa ...

  4. web 网站安全证书已过期或不可信 是否继续浏览

    发生环境:魅族MX4  uc浏览器 IIS部署SSL证书后提示不可信的解决方案 第一步:打开mmc——点击文件——添加删除管理单元——证书——计算机帐户 第二步:在计算机帐户的个人证书里面导入pfx格 ...

  5. 基于SAML的单点登录介绍

    http://blog.csdn.net/csethcrm/article/details/20694993 一.背景知识: SAML即安全断言标记语言,英文全称是Security Assertion ...

  6. Android项目svn代码管理问题[转]

    用svn控制版本,svn本身是不会识别哪些该传,哪些不该传,这就导致有些关于路径的东西(比如拓展jar的路径)也被上传了,而当别人下载后,那个路径对于这个人可能完全不存在,项目编译就会出问题.用ecl ...

  7. [TypeScript] Using Lodash in TypeScript with Typings and SystemJS

    One of the most confusing parts of getting started with TypeScript is figuring out how to use all th ...

  8. 详解Android动画之Tween Animation

    前面讲了动画中的Frame动画,今天就来详细讲解一下Tween动画的使用. 同样,在开始实例演示之前,先引用官方文档中的一段话: Tween动画是操作某个控件让其展现出旋转.渐变.移动.缩放的这么一种 ...

  9. 再次轻度破解EXE文件

    在经历股市多年的大起大落.大赚大赔之后.痛定思痛.深切感到在金融市场拼搏.必须建立健全交易纪律守则,严格运行. 这套完整的纪律守则,就是"交易系统". 在很多方面,它与一般的专家系 ...

  10. view中的setTag和getTag方法的理解

    下面是一段自定义适配器中的getView方法,其中使用了view的一个setTag和getTag方法 View中的setTag(Onbect)表示给View添加一个格外的数据(相当于缓存),以后可以用 ...