7.23 深搜广搜
深搜(DFS) 关键词:回溯
栈实现,(递归本质和栈一样)一直走到底再回溯,时间复杂度高,空间低
#include<iostream> #include<cstring> using namespace std; int R,C; char maps[40][40]; int dp[40][40]; int dir[2][4]={{1,0,0,-1},{0,1,-1,0}}; int ans=1<<30; void DFS(int x,int y,int step){ if(x>=R || y>=C || x<0 ||y<0) return ; if(x==R-1&&y==C-1) { if(step<ans) ans=step; return ; } if(maps[x][y]=='#') return; for(int i=0;i<4;i++){ if(x+dir[0][i] >=R || y+dir[1][i] >=C || x+dir[0][i] <0 || y+dir[1][i] < 0 ) continue; if(maps[x+dir[0][i]][y+dir[1][i]]=='.' && !dp[x+dir[0][i]][y+dir[1][i]]){ dp[x+dir[0][i]][y+dir[1][i]]=1; DFS(x+dir[0][i],y+dir[1][i],step+1);//也是一直走一直走,直到回溯的时候才置0; dp[x+dir[0][i]][y+dir[1][i]]=0; } } } int main(){ cin>>R>>C; char c; for(int i=0;i<R;i++) for(int j=0;j<C;j++){ cin>>c; maps[i][j]=c; dp[i][j]=0; } dp[0][0]=1; DFS(0,0,1); cout<<ans<<endl; return 0; }
广搜(BFS) 关键词:最优解 结构体 状态保存
第一次找到的就是最优解(最少步数,最少转弯次数),层次结构,一层一层地搜索,无回溯过程,队列实现,结构体记录状态,入队。
#include<iostream> #include<algorithm> #include<cstring> #include<queue> using namespace std; int R,C; char maps[40][40]; int dp[40][40]; int dir[2][4]={{1,0,0,-1},{0,1,-1,0}}; int ans=1<<30; struct node{ int x; int y; int step; node(int xx,int yy,int tt):x(xx),y(yy),step(tt){} }; int bfs() { queue<node> que; que.push(node(0,0,1)); while(!que.empty()){ node temp = que.front(); que.pop(); for(int i=0;i<4;i++){ //上下左右四方向 int tx=temp.x+dir[0][i]; int ty=temp.y+dir[1][i]; int tt=temp.step; if(dp[tx][ty]) continue; if(tx<0 || tx>=R || ty<0 || ty>=C ) continue; if(maps[tx][ty]=='#') continue; if(tx==R-1&&ty==C-1) return tt+1;//直接return,因为最先找到的一定是最优解 dp[tx][ty]=1; que.push(node(tx,ty,tt+1)); } } } int main(){ cin>>R>>C; for(int i=0;i<R;i++) for(int j=0;j<C;j++){ cin>>maps[i][j]; dp[i][j]=0; } int ans=bfs(); cout<<ans<<endl; return 0;
A题:
AveryBoy最近迷上了连连看游戏,于是他自己写了一个程序来玩,不过由于他学艺不精导致他写的连连看游戏连线不能从外面绕过。
游戏规则:在一个棋盘中,放了很多的棋子。如果某两个相同的棋子,可以通过一条线连起来(这条线不能经过其它棋子),而且线的转折次数不超过两次,那么这两个棋子就可以在棋盘上消去。玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序。
Input
输入数据有多组。每组数据的第一行有两个正整数n,m(0<n<=1000,0<m<1000),分别表示棋盘的行数与列数。
在接下来的n行中,每行有m个非负整数描述棋盘的方格分布。0表示这个位置没有棋子,正整数表示棋子的类型。接下来的一行是一个正整数q(0<q<50),表示下面有q次询问。在接下来的q行里,每行有四个正整数x1,y1,x2,y2,表示询问第x1行y1列的棋子与第x2行y2列的棋子能不能消去。n=0,m=0时,输入结束。
Output
Sample Input
3 4
1 2 3 4
0 0 0 0
4 3 2 1
4
1 1 3 4
1 1 2 4
1 1 3 3
2 1 2 4
3 4
0 1 4 3
0 2 4 1
0 0 0 0
2
1 1 2 4
1 3 2 3
0 0
Sample Output
YES NO NO NO NO YES
这题就是迷宫转弯问题,转弯次数小于等于2即可
#include <bits/stdc++.h> using namespace std; int n,m; int x2,y2; int maze[1010][1010]; int used[1010][1010]; int nx[4]={1,0,-1,0},ny[4]={0,1,0,-1}; struct node { int x,y; }; bool canmove(int x,int y) { if(x>=0&&x<n&&y>=0&&y<m&&maze[x][y]==0)return true; else if(x>=0&&x<n&&y>=0&&y<m&&maze[x][y]==maze[x2][y2])return true; else return false; } void bfs(int x,int y) { queue<node> que; node now,next; now.x=x; now.y=y; que.push(now); bool flag=false; while(!que.empty()) { now=que.front(); que.pop(); if(now.x==x2&&now.y==y2) { if(used[x2][y2]<=3) { flag=true; printf("YES\n"); } else break; } else { for(int i=0;i<4;i++) { next.x=now.x+nx[i]; next.y=now.y+ny[i]; while(canmove(next.x,next.y)) { if(used[next.x][next.y]==0) { used[next.x][next.y]=used[now.x][now.y]+1; que.push(next); } next.x+=nx[i]; next.y+=ny[i]; } } } } if(flag==false)printf("NO\n"); } int main() { while(1) { scanf("%d%d",&n,&m); if(n==0&&m==0)break; int i,j; for(i=0;i<n;i++) { for(j=0;j<m;j++)scanf("%d",&maze[i][j]); } int q; scanf("%d",&q); for(i=0;i<q;i++) { int x1,y1; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); x1--,x2--,y1--,y2--; if(maze[x1][y1]!=maze[x2][y2])printf("NO\n"); else if(maze[x1][y1]==0||maze[x2][y2]==0)printf("NO\n"); else { bfs(x1,y1); memset(used,0,sizeof used); } } } return 0; }
B题:
经典的迷宫转弯问题
Input
第1行为两个整数n, m (1 ≤ m, n ≤ 100),分别表示迷宫的行数和列数,接下来n行,每行包括m个字符。
每组测试数据的最后一行为5个整数k, x1, y1, x2, y2 (1 ≤ k ≤ 10, 1 ≤ x1, x2 ≤ n, 1 ≤ y1, y2 ≤ m),其中k表示AveryBoy最多能转的弯数,(x1, y1), (x2, y2)表示起点和终点。
Output
Sample Input
2
5 5
...**
*.**.
.....
.....
*....
1 1 1 3 1
5 5
...**
*.**.
.....
.....
*....
2 1 1 3 1
Sample Output
no yes
很经典的转弯问题
因为要看能不能过迷宫,利用广搜可以求出最少的转弯次数,如果最少转弯次数<=k,那么就可以通过迷宫
特别的是,因为起始方向不确定,所以最开始的转弯不计做一次转弯
开一个used[][]数组,记录当前点的转弯次数
先将used[][]全置为-1,(其实全置0也一样,最后比较的时候减去1就行)
定义now,next节点,对于每一个now节点,遍历它的四个方向
关键:找到一个方向,就一直把该方向走完,把这个方向上的所有未被标记过的节点的usd值全部标记,等于上一次转弯节点(now)的used值+1;
这里的判重即是判断used[x][y]是否等于-1,若已经标记过,那么我们跳过它,因为有的节点是交叉点
思路:对队列里面的坐标(x,y)做选择时,每次选择一个方向(共四个方向)走到不能走为止,将沿途经过的所有used值 = -1的点入队并更新used值(对used不为-1的点不作处理)。队列为空还无法到终点,输出no。若能遍历到终点,判断used值是否大于要求的k,若大于输出no,否则输出yes。
#include <bits/stdc++.h> using namespace std; int used[110][110]; int n,m; int k,x2,y2; char maze[110][110]; int nx[4]={1,0,-1,0},ny[4]={0,1,0,-1}; struct node { int x,y; }; bool canmove(int x,int y) { if(x>=0&&x<n&&y>=0&&y<m&&maze[x][y]=='.')return true; else return false; } void bfs(int x,int y) { queue<node> que; node now,next; now.x=x; now.y=y; que.push(now); bool flag=false; while(!que.empty()) { now=que.front(); que.pop(); if(now.x==x2&&now.y==y2) { if(used[x2][y2]<=k) { flag=true; printf("yes\n"); } else break; } else { for(int i=0;i<4;i++) { next.x=now.x+nx[i]; next.y=now.y+ny[i]; while(canmove(next.x,next.y)) { if(used[next.x][next.y]==-1) { used[next.x][next.y]=used[now.x][now.y]+1; que.push(next); } next.x+=nx[i]; next.y+=ny[i]; } } } } if(flag==false)printf("no\n"); } int main() { int t; cin>>t; while(t--) { int i,j; scanf("%d%d",&n,&m); for(i=0;i<n;i++) { scanf("%s",maze[i]); } int x1,y1; scanf("%d%d%d%d%d",&k,&x1,&y1,&x2,&y2); x1--,y1--,x2--,y2--; for(i=0;i<n;i++) { for(j=0;j<m;j++)used[i][j]=-1; } bfs(x1,y1); } return 0; }
C题:
这次AveryBoy被困在一个三维迷宫中,他必须想办法在T分钟内离开迷宫(包括T)。迷宫是一个A*B*C的立方体,起点在(0,0,0)位置,终点在(A-1,B-1,C-1)位置。如果他能离开迷宫,输出离开迷宫所需最短时间,否则输出-1。
三维迷宫问题,仍然是找最优解,求最小步数(时间)在二维基础上修改结构体内x,y为x,y,z,修改方向为x,y,z方向上的六个方向即可,稍加变通,不要被吓到。
#include <bits/stdc++.h> using namespace std; int maze[55][55][55]; int nx[6]={1,-1,0,0,0,0}; int ny[6]={0,0,1,-1,0,0}; int nz[6]={0,0,0,0,1,-1}; int dp[55][55][55]; int X,Y,Z; struct node { int x,y,z,step; node(int xx,int yy,int zz,int ss):x(xx),y(yy),z(zz),step(ss){} }; int bfs() { queue<node> que; que.push(node(0,0,0,0)); bool flag=false; while(!que.empty()) { node temp = que.front(); que.pop(); for(int i=0;i<6;i++) { //上下左右四方向 int tx=temp.x+nx[i]; int ty=temp.y+ny[i]; int tz=temp.z+nz[i]; int tt=temp.step; if(dp[tx][ty][tz]) continue; if(tx<0 || tx>=X || ty<0 || ty>=Y||tz<0||tz>=Z ) continue; if(maze[tx][ty][tz]==1) continue; if(tx==X-1&&ty==Y-1&&tz==Z-1) { flag=true; return tt+1;//直接return,因为最先找到的一定是最优解 } dp[tx][ty][tz]=1; que.push(node(tx,ty,tz,tt+1)); } } if(flag==false)return -1; } int main() { int t; cin>>t; while(t--) { int tt; scanf("%d%d%d%d",&X,&Y,&Z,&tt); int i,j,k; for(i=0;i<X;i++) { for(j=0;j<Y;j++) { for(k=0;k<Z;k++) { scanf("%d",&maze[i][j][k]); } } } memset(dp,0,sizeof dp); int ans=bfs(); if(ans<=tt&&ans!=-1)printf("%d\n",ans); else printf("-1\n"); } return 0; }
D题:
如下图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
经典的八数码问题,依然找最优解,用字符串记状态,结构体数组内放入现在数码块的状态char sz[3][3],开一个map<string,int>记录该状态是否已出现过,用x,y记录0的位置
剪枝,奇排列永远不能变成偶排列
#include<bits/stdc++.h> using namespace std; char s1[10],f1[10]; bool nixu() { int ans1=0; int ans2=0; int i,j; string ss1="",ff1=""; for(i=0;i<9;i++) { if(s1[i]!='.')ss1+=s1[i]; if(f1[i]!='.')ff1+=f1[i]; } //cout<<ss1<<endl; //cout<<ff1<<endl; for(i=1;i<8;i++) { for(j=0;j<8-i;j++) { if(ss1[j]>ss1[j+1]) { char t=ss1[j]; ss1[j]=ss1[j+1]; ss1[j+1]=t; ans1++; } } } for(i=1;i<8;i++) { for(j=0;j<8-i;j++) { if(ff1[j]>ff1[j+1]) { char t=ff1[j]; ff1[j]=ff1[j+1]; ff1[j+1]=t; ans2++; } } } //cout<<ans1<<' '<<ans2; if(ans1%2!=ans2%2)return false; else return true; } struct node { int x,y,step; //x,y记下0(.)的位置 char sz[3][3]; }; char start[10],finally[10]; string temp; map<string,bool> mp;//代表状态有无被标记 字符串代表状态 int inf=999999999; int nx[4]={1,0,0,-1},ny[4]={0,1,-1,0}; int bfs() { int ans=inf; node now,next; int d=0,i,j; for(i=0;i<3;i++) { for(j=0;j<3;j++) { now.sz[i][j]=start[d++]; if(now.sz[i][j]=='.') { now.x=i; now.y=j; } } } now.step=0; queue<node> que; que.push(now); while(!que.empty()) { now=que.front(); que.pop(); for(i=0;i<4;i++) { next=now; //让这两个状态下的字符数组都相同 next.x=now.x+nx[i]; next.y=now.y+ny[i]; if(next.x<0||next.x>2||next.y<0||next.y>2)continue; next.step=now.step+1; next.sz[now.x][now.y]=next.sz[next.x][next.y];//now.x,now.y代表的就是先前状态下的0的位置 next.sz[next.x][next.y]='.';//与0交换 temp=""; for(j=0;j<3;j++) { for(int k=0;k<3;k++) temp+=next.sz[j][k]; } if(temp==finally) { ans=min(ans,next.step); } if(!mp[temp]) { mp[temp]=true; que.push(next); } } } return ans; } int main() { int t; cin>>t; while(t--) { scanf("%s%s",start,finally); strcpy(s1,start); strcpy(f1,finally); if(!nixu())printf("-1\n"); else { int ans=bfs(); //cout<<ans<<endl; if(ans==inf)printf("-1\n"); else printf("%d\n",ans); } } return 0; }