标签:style blog http color 使用 os strong io
图的遍历也称为搜索,就是从图中某个顶点出发,沿着一些边遍历图中所有的顶点,且每个顶点仅被访问一次,遍历可采取两种不同的方式:深度优先搜索(DFS)和广度优先搜索(BFS)。
1.DFS算法思想`
从顶点v出发深度遍历图G的算法
① 访问v0顶点,置vis[v0]=1,搜索v0未被访问的邻接点w,若存在邻接点w,则dfs(w),直到到达所有邻接点都被访问过的顶点u为止,接着退回一步,看是否还有其他没有被访问的邻接点。如果有,则访问此顶点,进行前述类似的访问,如果没有,就在退回一步进行搜索,重复上述过程直到所有顶点都被访问过。
② dfs算法最大特色就在于其递归特性,使得算法代码简洁。
2. BFS算法思想
从顶点v出发广度遍历图G的算法
① 访问v0顶点,置vis[v0]=1,依次访问顶点v0各个未被访问的邻接点,将v0各个未被访问的邻接点都访问到,分别从这些邻接点出发,依次访问他们未被访问的邻接点,直到所有顶点都被访问过。常用队列实现。
② 非递归,好理解。
基本思想讲完了,下面说说今天做的四道搜索题吧。
1.POJ2251 Dungeon Master:http://poj.org/problem?id=2251
题意:给出一三维空间的地牢,要求求出由字符‘S‘到字符‘E‘的最短路径
移动方向可以是上,下,左,右,前,后,六个方向
每移动一次就耗费一分钟,要求输出最快的走出时间。
不同L层的地图,相同RC坐标处是连通的
思路:这道题和之前做的走迷宫什么的类似,就是多了一维,做的时候把每一维的次序弄乱了,调了很久才搞清楚。
int dx[6]= {0,0,0,0,1,-1}; int dy[6]= {0,0,1,-1,0,0}; int dz[6]= {1,-1,0,0,0,0};
用三个数组分别存x方向,y方向,z方向的操作。我用的BFS,建图的时候建对,就没什么问题了。
附上代码:
1 #include<stdio.h> 2 #include<queue> 3 #include<string.h> 4 using namespace std; 5 int visit[40][40][40]; 6 int step[40][40][40]; 7 int dx[6]= {0,0,0,0,1,-1}; 8 int dy[6]= {0,0,1,-1,0,0}; 9 int dz[6]= {1,-1,0,0,0,0}; 10 int n,m,k,flag; 11 struct node 12 { 13 int x,y,z; 14 } start,end; 15 int bfs() 16 { 17 flag=0; 18 queue <node> Q; 19 Q.push(start); 20 node temp; 21 visit[start.x][start.y][start.z]=1; 22 while(!Q.empty()) 23 { 24 temp=Q.front(); 25 Q.pop(); 26 if(temp.x==end.x&&temp.y==end.y&&temp.z==end.z) 27 { 28 flag=1; 29 break; 30 } 31 int x=temp.x; 32 int y=temp.y; 33 int z=temp.z; 34 for(int i=0; i<6; i++) 35 { 36 if(!visit[z+dz[i]][x+dx[i]][y+dy[i]]&&x+dx[i]<m&&x+dx[i]>=0&&y+dy[i]<k&&y+dy[i]>=0&&z+dz[i]<n&&z+dz[i]>=0) 37 { 38 temp.x=x+dx[i]; 39 temp.y=y+dy[i]; 40 temp.z=z+dz[i]; 41 visit[temp.z][temp.x][temp.y]=1; 42 Q.push(temp); 43 step[temp.x][temp.y][temp.z]=step[x][y][z]+1; 44 } 45 } 46 } 47 return flag; 48 } 49 int main() 50 { 51 int i,j,t; 52 char ch; 53 while(scanf("%d%d%d",&n,&m,&k)!=EOF) 54 { 55 if(n==0&&m==0&&k==0) 56 break; 57 memset(step,0,sizeof(step)); 58 getchar(); 59 for(i=0; i<n; i++) 60 { 61 for(j=0; j<m; j++) 62 { 63 for(t=0; t<k; t++) 64 { 65 scanf("%c",&ch); 66 if(ch==‘.‘) 67 visit[i][j][t]=0; 68 else if(ch==‘#‘) 69 visit[i][j][t]=1; 70 else if(ch==‘S‘) 71 { 72 visit[i][j][t]=0; 73 start.x=j; 74 start.y=t; 75 start.z=i; 76 } 77 else if(ch==‘E‘) 78 { 79 visit[i][j][t]=0; 80 end.x=j; 81 end.y=t; 82 end.z=i; 83 } 84 } 85 getchar(); 86 } 87 if(i!=n-1) 88 getchar(); 89 } 90 if(bfs()) 91 printf("Escaped in %d minute(s).\n",step[end.x][end.y][end.z]); 92 else 93 printf("Trapped!\n"); 94 } 95 return 0; 96 }
2.POJ 1426 Find The Multiple :http://poj.org/problem?id=1426
题意: 给出n,求由0或1组成的一个整数能整除n。
思路:我是暴力加bfs水过的,用C++交时间超限了,看到有人说用G++交不会超时,一交果然AC了。。不会优化,先附上丑陋代码:
1 #include<stdio.h> 2 #include<queue> 3 #include<string.h> 4 using namespace std; 5 int n; 6 long long bfs() 7 { 8 queue<long long> Q; 9 while(!Q.empty()) 10 { 11 Q.pop(); 12 } 13 Q.push(1); 14 while(1) 15 { 16 long long sum=Q.front(); 17 if(sum%n==0) 18 return sum; 19 Q.pop(); 20 Q.push(sum*10); 21 Q.push(sum*10+1); 22 } 23 } 24 int main() 25 { 26 while(scanf("%d",&n)!=EOF&&n) 27 { 28 printf("%lld\n",bfs()); 29 } 30 return 0;
看到网上别人思路:
解题思路:
首先暴力枚举肯定是不可能的 1000ms 想不超时都难,而且枚举还要解决大数问题。。
要不是人家把这题放到搜索,怎么也想不到用BFS。。。
解题方法: BFS+同余模定理
不说废话
首先说说朴素的不剪枝搜索方法:
我以n=6为例
首先十进制数,开头第一个数字(最高位)一定不能为0,即最高位必为1
设6的 ”01十进制倍数” 为k,那么必有k%6 = 0
现在就是要用BFS求k值
1、先搜索k的最高位,最高位必为1,则此时k=1,但1%6 =1 != 0
因此k=1不是所求,存储余数 1
2、搜索下一位,下一位可能为0,即 k*10+0,此时k=10,那么k%6=4
可能为1,即 k*10+1,此时k=11,那么k%6=5
由于余数均不为0,即k=10与k=11均不是所求
3、继续搜索第三位,此时有四种可能了:
对于k=10,下一位可能为0,即 k*10+0,此时k=100,那么k%6=4
下一位可能为1,即 k*10+1,此时k=101,那么k%6=5
对于k=11,下一位可能为0,即 k*10+0,此时k=110,那么k%6=2
下一位可能为1,即 k*10+1,此时k=111,那么k%6=3
由于余数均不为0,即k=100,k=101,k=110,k=111均不是所求
4、继续搜索第四位,此时有八种可能了:
对于k=100,下一位可能为0,即 k*10+0,此时k=1000,那么k%6=4
下一位可能为1,即 k*10+1,此时k=1001,那么k%6=5
对于k=101,下一位可能为0,即 k*10+0,此时k=1010,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1011,那么k%6=3
对于k=110,下一位可能为0,即 k*10+0,此时k=1100,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1101,那么k%6=3
对于k=111,下一位可能为0,即 k*10+0,此时k=1110,那么k%6=0
下一位可能为1,即 k*10+1,此时k=1111,那么k%6=1
我们发现k=1110时,k%6=0,即1110就是所求的倍数
从上面的演绎不难发现,用BFS是搜索 当前位数字 (除最高位固定为1),因为每一位都只有0或1两种选择,换而言之是一个双入口BFS
本题难点在于搜索之后的处理:对余数的处理,对大数的处理,余数与所求倍数间的关系
接下来说说处理大数问题和剪枝的方法:
首先我们简单回顾一下 朴素搜索 法:
n=6
1%6=1 (k=1)
{
(1*10+0)%6=4 (k=10)
{
(10*10+0)%6=4 (k=100)
{
(100*10+0)%6=4 (k=1000)
(100*10+1)%6=5 (k=1001)
}
(10*10+1)%6=5 (k=101)
{
(101*10+0)%6=2 (k=1010)
(101*10+1)%6=3 (k=1011)
}
}
(1*10+1)%6=5 (k=11)
{
(11*10+0)%6=2 (k=110)
{
(110*10+0)%6=2 (k=1100)
(110*10+1)%6=3 (k=1101)
}
(11*10+1)%6=3 (k=111)
{
(111*10+0)%6=0 (k=1110) 有解
(111*10+1)%6=1 (k=1111) 由于前面有解,这个余数不存储
}
}
}
从上面可以看出余数的存数顺序(逐层存储):
用数组mod[]存储余数,其中mod[0]不使用,由mod[1]开始
那么mod中的余数依次为: 1 4 5 4 5 2 3 4 5 2 3 2 3 0 共14个
即说明我们得到 余数0 之前,做了14步*10的操作,那么当n值足够大的时候,是很容易出现k为大数的情况(事实上我做过统计,200以内的n,有18个n对应的k值为大数
那么我们再用int去存储k就显得不怎么明智了。
为了处理所有情况,我们自然会想到 是不是应该要用int[]去存储k的每一位?
而又由于k是一个01序列,那能不能把 *10得到k每一位的问题 转化为模2的操作得到k的每一位(0或1) 呢?
答案是可以的
首先我们利用 同余模定理 对得到余数的方式进行一个优化
(a*b)%n = (a%n *b%n)%n
(a+b)%n = (a%n +b%n)%n
随便抽取上面一条式子为例
前一步 (11*10+1)%6=2 即k=110 , k%6=2
当前步 (110*10+1)%6=2
由同余模定理 (110*10+1)%6 = ((110*10)%6+1%6 )%6 = ((110%6 * 10%6)%6 +1 )%6
不难发现下划线部分110%6等于 (11*10+0)%6 = 2
所以当前步(110*10+1)%6可以转变为 (2*10+1)%6=2
很显然地,这种处理把k=110 等价于 k=2
即用 前一步操作得到的余数 代替 当前步的k值
而n在200的范围内, 余数值不可能超过3位数, 这就解决了 大数的问题
通过这种处理手法,我们只需在BFS时顺手存储一个 余数数组mod[] ,就能通过mod[i-1]得到mod[i] ,直到mod[i]==0 时结束,大大减少了运算时间
前面已经提到,n=6时,求余操作进行了14次,对应地,BFS时*10的操作也进行了14次。
令i=14,通过观察发现,i%2恰好就是 6 的倍数的最低位数字
i/2 再令 i%2 ,恰好就是 6 的倍数的 次低位数字。。。
循环这个操作,直到i=0,就能得到 6的 01倍数(一个01队列),倒序输出就是所求
这样就完成了 *10操作到 %2操作的过渡
由于n值有限,只是1到200的整数,因此本题也可以用打表做,通过上面的方法得到结果后,就把1~200的倍数打印出来,重新建立一个程序,直接打表就可以了。
不过打表比上面介绍的方法快不了多少
代码:
1 //Memory Time 2 //2236K 32MS 3 4 #include<iostream> 5 using namespace std; 6 7 int mod[524286]; //保存每次mod n的余数 8 //由于198的余数序列是最长的 9 //经过反复二分验证,436905是能存储198余数序列的最少空间 10 //但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE 11 12 int main(int i) 13 { 14 int n; 15 while(cin>>n) 16 { 17 if(!n) 18 break; 19 20 mod[1]=1%n; //初始化,n倍数的最高位必是1 21 22 for(i=2;mod[i-1]!=0;i++) //利用同余模定理,从前一步的余数mod[i/2]得到下一步的余数mod[i] 23 mod[i]=(mod[i/2]*10+i%2)%n; 24 //mod[i/2]*10+i%2模拟了BFS的双入口搜索 25 //当i为偶数时,+0,即取当前位数字为0 。为奇数时,则+1,即取当前位数字为1 26 27 i--; 28 int pm=0; 29 while(i) 30 { 31 mod[pm++]=i%2; //把*10操作转化为%2操作,逆向求倍数的每一位数字 32 i/=2; 33 } 34 while(pm) 35 cout<<mod[--pm]; //倒序输出 36 cout<<endl; 37 } 38 return 0; 39 }
3.POJ 3087 Shuffle‘m Up:http://poj.org/problem?id=3087
题意:
已知两堆牌s1和s2的初始状态, 其牌数均为c,按给定规则能将他们相互交叉组合成一堆牌s12,再将s12的最底下的c块牌归为s1,最顶的c块牌归为s2,依此循环下去。
现在输入s1和s2的初始状态 以及 预想的最终状态s12
问s1 s2经过多少次洗牌之后,最终能达到状态s12,若永远不可能相同,则输出"-1"。
思路:从题意来看,直接模拟就好了,用不到BFS。好吧,我也的确直接用模拟做了,
附上代码:
1 #include<stdio.h> 2 #include<string.h> 3 int main() 4 { 5 int t,l,i,j; 6 char s1[101],s2[101],s[205],s12[205],s3[205]; 7 scanf("%d",&t); 8 for(j=1; j<=t; j++) 9 { 10 int ans=0; 11 scanf("%d",&l); 12 getchar(); 13 gets(s1); 14 gets(s2); 15 gets(s12); 16 strcpy(s,s1); 17 strcpy(s+l,s2); 18 s3[0]=‘\0‘; 19 while(strcmp(s3,s12)!=0) 20 { 21 for(i=0; i<l; i++) 22 { 23 s3[i*2+1]=s1[i]; 24 s3[i*2]=s2[i]; 25 } 26 s3[2*l]=‘\0‘; 27 ans++; 28 if(strcmp(s3,s)==0) 29 { 30 ans=-1; 31 break; 32 } 33 for(i=0; i<l; i++) 34 s1[i]=s3[i]; 35 strcpy(s2,s3+l); 36 } 37 printf("%d %d\n",j,ans); 38 } 39 return 0; 40 }
4.POJ 2488 A Knight‘s Journey:http://poj.org/problem?id=2488
题意:
马从A1开始走“日”,如果能走遍棋盘上所有点,输出路径,否则,输出impossible.
思路:
定义了一个结构体变量,存该step时的位置。
int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};可以保证最后按字典序输出。
做的时候还是没按字典序输出,最后发现是行和列搞反了。
附上代码:
1 #include<stdio.h> 2 #include<string.h> 3 struct node 4 { 5 char c; 6 char z; 7 }move[900]; 8 int n,m; 9 int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}}; 10 int visit[30][30]; 11 int flag; 12 int dfs(int x,int y,int step) 13 { 14 int xx,yy,i; 15 if(step==n*m) 16 { 17 flag=1; 18 return 1; 19 } 20 for(i=0; i<8; i++) 21 { 22 xx=x+dir[i][0]; 23 yy=y+dir[i][1]; 24 if(xx>=1 && xx<=m&& yy>=1 && yy<=n&& !visit[xx][yy]) 25 { 26 move[step].c=xx-1+‘A‘; 27 move[step].z=yy+‘0‘; 28 visit[xx][yy]=1; 29 dfs(xx,yy,step+1); 30 if(flag) 31 return 1; 32 visit[xx][yy]=0; 33 } 34 } 35 return 0; 36 } 37 int main() 38 { 39 int t,i,k; 40 scanf("%d",&t); 41 for(k=1; k<=t; k++) 42 { 43 flag=0; 44 memset(visit,0,sizeof(visit)); 45 memset(move,0,sizeof(move)); 46 scanf("%d%d",&n,&m); 47 visit[1][1]=1; 48 move[0].c=‘A‘; 49 move[0].z=‘1‘; 50 printf("Scenario #%d:\n",k); 51 if(dfs(1,1,1)) 52 { 53 for(i=0;i<n*m;i++) 54 { 55 printf("%c%c",move[i].c,move[i].z); 56 } 57 } 58 else 59 printf("impossible"); 60 printf("\n\n"); 61 } 62 return 0; 63 }
总结完了。。弱菜要去看昨晚Bestcoder的题了,噶呜~
搜索专题小结及例题:POJ2251&POJ1426&POJ3087&POJ2488,布布扣,bubuko.com
搜索专题小结及例题:POJ2251&POJ1426&POJ3087&POJ2488
标签:style blog http color 使用 os strong io
原文地址:http://www.cnblogs.com/PJQOOO/p/3888899.html