原题地址:http://acm.fzu.edu.cn/problem.php?pid=1892
这题一看就能知道是BFS,而且数据小,不会超时。但是接口的问题怎么处理,这个是重点。
题目很明显,管子就是状态压缩。题目第5点:游戏中有四个方向,分别表示东南西北,可用数字编号说明水管中水流的方向,1表示可以向东流,2表示可以向南流,4表示可以向西流,8表示可以向北流。1,2,4,8就是二进制数的4个位,编号15的管子,就是1+2+4+8=15,说明水管通向东南西北四个方向。同理,编号为9的管子,1+8=9,通向东和北。如此类推。
那么问题来了,分析一个管子通向哪几个方向,要怎么实现?之前学过点状压基础的就能知道,用位运算操作。
位运算:<<,左移 位运算,将数的二进制数向左移n位,左边的舍弃,右边补0 。 1<<n就是将1向左移动n位,左边舍弃,右边补0,其数值上等效于1*(2^n)。比如,1<<3,本来1的二进制数是0001,左移3位后,变为1000,等于8,2^3 。
位运算:&,且 位运算,对两个数的二进制数按位进行且运算,同为1,则该位为1 ,否则为0。如下:
00101
11100
----------------
00100
那么,结合这两个运算就能知道,管子通往哪几个方向。实现代码如下:
for(i=0; i<4; i++)
{
if( ( n&(1<<i) ) >0 )
{
// 对应的二进制第 i 位就是1,就是这个位代表的方向可以流通
}
}
那么接下来,判断相邻的两个管子能不能接上,也就好办了。就是先判断管子通向哪个方向,然后判断那个方向的相邻管子也可以通向反方向。
左边管子方向朝东,它东边的管子可以通向西,那么它们就连通了。实现代码如下:
// 地图用二维的 int 储存
int map[50][50] ;
// 定义结构体,now,表示当前坐标;
// 用来移动的数组
int xx[4]= {0,1,0,-1};
int yy[4]= {1,0,-1,0};
for(i=0; i<4; i++)
{
if( (maps[now.x][now.y]&(1<<i) ) >0 )
{
f( (maps[ now.x+xx[i] ] [ now.y+yy[i] ] ) & ( 1<<( (2+i)%4) ) ) >0 )
{
// 连通!!(有没有感觉酱紫很燃~2333)
}
}
}
会了这些之后,再用BFS就没问题了~但是位运算让我很头大,细节问题让我调试了很久,比赛时没做出来,当天晚上改到凌晨1点多,但依然没对,还是第二天改对的……
AC代码如下:
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<queue> using namespace std; int maps[50][50]; bool book[50][50]; int xx[4]= {0,1,0,-1}; int yy[4]= {1,0,-1,0}; int t,n; int x1,x2,y3,y2,i,j; int flag=0; int sum=0; int k=0; int num=0; struct node { int x,y,com,step; }; node now; node next; queue <node> qq; int main() { scanf("%d",&t); while(t--) { scanf("%d",&n); { num=0; // 注意,入口和出口都只有一个方向。减去16或32就是管子的出入口方向 for(i=0; i<n; i++) { for(j=0; j<n; j++) { scanf("%d",&maps[i][j]); if(maps[i][j]>=32) { maps[i][j]-=32; x2=i; y2=j; } else if(maps[i][j]>=16) { maps[i][j]-=16; x1=i; y3=j; } if(maps[i][j]!=0) { num++; } } } /* for(int i=0;i<n;i++) { for(int j=0;j<n;j++) { printf("%d ",maps[i][j]); } printf("\n"); } // */ next.x=x1; next.y=y3; next.step=0; flag=0x3f3f3f3f;//大于10^9且在int范围内,挺好用的一个最大值 sum=0; for(i=0; i<4; i++) { if((maps[x1][y3]&(1<<i))>0) { next.com=(i+2)%4;//判断起始点的入水方向(起始点出水方向的反方向),以备后面使用 //printf("%d\n",next.com); break; } } qq.push(next); memset(book,0,sizeof(book)); while(!qq.empty()) { now=qq.front(); qq.pop(); //printf("$%d\n",k++); //判断两步内是否会结束游戏(到终点,或者水流到管子外) for(i=0; i<4; i++) { if((maps[now.x][now.y]&(1<<i))>0)//找到所有方向 //不为终点,不越界,并且能连通 if( ((now.x+xx[i])!=x2||(now.y+yy[i])!=y2) && ((now.x+xx[i])>=0) && ((now.x+xx[i])<n) && ((now.y+yy[i])>=0) && ((now.y+yy[i])<n) && ((maps[now.x+xx[i]][now.y+yy[i]])&( 1<<((2+i))%4) )>0) { for(j=0; j<4; j++)//下一步依旧不为终点,不越界,能连通 { if((maps[now.x+xx[i]][now.y+yy[i]]&(1<<j))>0) if( ((now.x+xx[i]+xx[j])!=x2||(now.y+yy[i]+yy[j])!=y2) && ((now.x+xx[i]+xx[j])>=0) && ((now.x+xx[i]+xx[j])<n) && ((now.y+yy[i]+yy[j])>=0) && ((now.y+yy[i]+yy[j])<n) && ((maps[now.x+xx[i]+xx[j]][now.y+yy[i]+yy[j]])&( 1<<((2+j)%4)) )>0) { } else//否则标记步数,并且步数取最小值 { flag=min(flag,now.step+1); //printf("^^%d\n",flag); } } } else//否则标记结束时刻的步数 { flag=min(flag,now.step); //printf("^%d\n",flag); } } for(i=0; i<4; i++) { if(((maps[now.x][now.y]&(1<<i))>0)&&(now.com!=i))//除了进水方向的出水方向(水不能从原路返回) { //下一个管子没流到过,不为终点不越界并且可以连通 if(!book[now.x+xx[i]][now.y+yy[i]] && ((now.x+xx[i])!=x2||(now.y+yy[i])!=y2) && ((now.x+xx[i])>=0) && ((now.x+xx[i])<n) && ((now.y+yy[i])>=0) && ((now.y+yy[i])<n) && ((maps[now.x+xx[i]][now.y+yy[i]])&( 1<<((2+i)%4)) )>0 ) { //printf("!!%d %d\n",now.x+xx[i],now.y+yy[i]); sum++;//答案++ book[now.x+xx[i]][now.y+yy[i]]=1;//标记 next.x=now.x+xx[i]; next.y=now.y+yy[i]; next.com=(i+2)%4; next.step=now.step+1; if(next.step<=flag)//下一步不会到终点,不会越界流出,那么才压入队列。flag在之前已经判断过,为游戏结束的步数 { qq.push(next); //printf("%d %d %d\n",now.x+xx[i],now.y+yy[i],now.step); //getchar(); } } } } } printf("%d\n",sum); while(!qq.empty()) { qq.pop(); } } } return 0; } /* 给你多提供一组数据测试使用~不用谢~我叫红领巾~ Simple input: 4 4 18 15 15 15 9 15 15 15 15 15 15 15 15 15 15 40 5 3 5 5 6 0 10 3 5 15 6 10 10 0 10 40 10 9 5 12 18 9 5 5 5 12 5 3 5 5 6 0 10 0 0 15 6 10 10 0 0 40 10 9 5 12 18 9 5 5 5 12 Simple output: 10 18 14 */
最后,女神镇宅~