算法专题训练 搜索a-T3 Ni骑士(ni)

时间:2024-03-20 22:03:20

  搞了半天八数码弄不出来就只好来打题解  这道题是在搜索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: 算法专题训练 搜索a-T3  Ni骑士(ni)

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的“原理”  它是从近往远的每一排节点进行拓展的 也就是说 下一次取队头出来的肯定是要比这次大的  好  那么在有了理论依据和现实基础后  我果断的在进行是否拓展过的判断之前加了这样一个条件

算法专题训练 搜索a-T3  Ni骑士(ni)

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

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

算法专题训练 搜索a-T3  Ni骑士(ni)

  然后终于   第十个点在教室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.山外有山,码外有码     或许换种方式  算法会有更大的改进

算法专题训练 搜索a-T3  Ni骑士(ni)

  另外 ,至于最后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 ;
}