(图文无关 雾
搜索是一个NOIP当中经常出现的考点,其实搜索换个方式来想也无非就是让电脑来帮你试,最后得到一个结果,当然这么口胡谁都会,那么我们就来看看搜索当中的一个大部分:
BFS(广度优先搜索)
首先我们看这样一道例题
P1451 求细胞数量
这个题吧,其实是我学习BFS遇到的第一道题,当然他作为一个很好的入门题让我很快的学会了BFS的基本模板
大概总结一下就是这样的
struct node{ // 你要表示的状态,例如:坐标 } node _node(/*参数*/){ node r; //初始化 return r; } /* 例如: struct node{ int x,y; } node _node(int x,int y){ node r; r.x=x; r.y=y; return r; } */ queue<node>q; bool vis[...]...[...];//判重数组,保证每个状态只访问一次。 void bfs(node s/*起始元素*/){ q.push(s); vis[...]...[...]=1;//初始状态纪录 while(!q.empty()){ node head=q.front(); q.pop(); for(...){//遍历所有能到达的状态 node new=_node(head...)//新状态 if(...new...&&!vis[...]...[...]){//新状态合法且没被访问过 q.push(new); vis[...]...[...]=1;//新状态纪录。 } } } }
当然还是WZ大佬比较强%%%%%%%%%
然后吧我们来看一看这个题
其实是这样的
我们先写好上下左右四个方向的代码
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
然后再计算的时候用一个for循环实现就行了具体的思路是这样的
找到一个点的x,y---->对于这个点上下左右(四联通)进行判断,如果是细胞就把其坐标压进队列,如果不是就跳过,当我们处理完一个细胞(假设他周围有细胞的话),就会继续处理它周围的细胞,知道把所有细胞的四联通都找完为止,对了我们把一个细胞找完之后要把它清为false,不然会出错
上代码
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; bool b[101][101]; int n, m, a[101][101], ans = 0; int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; inline void qaq(int x, int y) { int hx[1000], hy[1000], head = 1, tail = 1, tx, ty; ans++; hx[1] = x, hy[1] = y; b[x][y] = false; for (; head <= tail; ++head) { for (int i = 0; i <= 3; ++i) { tx = hx[head] + dx[i], ty = hy[head] + dy[i]; if (tx > 0 && tx <= m && ty > 0 && ty <= n && b[tx][ty]) { tail++; hx[tail] = tx, hy[tail] = ty; b[tx][ty] = false; } } } } int main() { scanf("%d%d", &m, &n); for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) b[i][j] = true; for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) { scanf("%1d", &a[i][j]); if (!a[i][j]) b[i][j] = false; } for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) if (b[i][j]) qaq(i, j); printf("%d", ans); return 0; }
T2
P1162 填涂颜色
这个题其实和上一道差不多,也是找连通块然后进行处理,那么做这个题的时候我们需要考虑些什么呢?
首先,因为涂色区域一定是被包围着的,所以他一定是2~~~~n-1这个范围内的,循环的时候就可以这么写(不过不影响大局)
然后他说只把中间那个区域涂上颜色,换个思路就是我们把所有边上的连通块全都处理掉不就好了嘛~
上代码
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int n, a[31][31], dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1}, X, Y; bool b[31][31]; void qaq(int x, int y) { int q[900][2], head = 1, tail = 0; tail++; q[tail][1] = x; q[tail][2] = y; b[x][y] = 1; for (; head <= tail; ++head) { for (int i = 0; i <= 3; ++i) { X = q[head][1] + dx[i]; Y = q[head][2] + dy[i]; if (X > 0 && X <= n && Y > 0 && Y <= n && b[X][Y] == 0) { tail++; q[tail][1] = X; q[tail][2] = Y; b[X][Y] = 1; } } } } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) { scanf("%d", &a[i][j]); if (a[i][j]) b[i][j] = 1; } //printf(" "); for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) if (!a[i][j] && (i == 1 || j == 1 || i == n || j == n))//这里就是保证连通块必然有至少一个数字在边上 qaq(i, j); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { if (!b[i][j]) printf("2 "); else printf("%d ", a[i][j]); } printf(" "); } }
现在我们来看BFS的另外一个大类型
迷宫问题~~~~~
说真的,做到这东西的时候,我的内心是这样的
因为迷宫我一开始不会,就先做了神奇题目_GC滑迷宫(毕竟是自己参与题目构思的嘛~)
题目有限制同学们就自己去团队里头找就好啦
首先这个题的思路是把地图读入,然后从起始坐标开始搜
因为_GC只能滑着走啊,所以这货就直接让他撞墙(a[x][y]==1)的时候换个方向继续划水就行了
有两个操作比较好
判出界操作和判初始位置这个操作(虽然正常人测试点不会这么诡异)
上代码(虽然黈力,WZ别打我)
#include <iostream> #include <cstdio> #include <queue> #include <algorithm> using namespace std; int n, m, sx, sy; bool mat[501][501]; const int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0}; struct pos { int x, y, step; pos(int x, int y, int step) : x(x), y(y), step(step) {} }; queue<pos> q; bool vis[501][501]; inline bool pan(int x, int y)//判断合法性 { return x >= 1 && x <= n && y >= 1 && y <= m && mat[x][y] == 0; } bool out(int x, int y)//判断是否已到达迷宫边界,即可以离开迷宫 { return (x == 1 || x == n || y == 1 || y == m) && mat[x][y] == 0; } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> mat[i][j]; } } cin >> sx >> sy; if(mat[sx][sy]){//如果初始位置是墙,则无解 cout<<-1; return 0; } if(out(sx,sy)){//如果初始位置在边界,则直接离开 cout<<0; return 0; } q.push(pos(sx, sy, 0)); vis[sx][sy] = 1; while (!q.empty()) { pos h = q.front(); q.pop(); // cout << h.x << ' ' << h.y << endl; if (out(h.x, h.y))//如果到达出口 { cout << h.step - 1;//因为原题求的是撞墙次数,而我们知道最后一次是不撞墙的 return 0;//直接退出,因为已求得最优解 } for (int i = 0; i < 4; i++) { int xx = h.x, yy = h.y; while (pan(xx + dx[i], yy + dy[i]))//如果没撞到墙,就继续走 { xx += dx[i]; yy += dy[i]; } if ((xx == h.x && yy == h.y) || vis[xx][yy])//如果并没有移动,或者最终位置已到达过 continue; vis[xx][yy] = 1; q.push(pos(xx, yy, h.step + 1)); } } cout << -1;//如果所有可能节点都已遍历,而始终没有到达边界,则无解 }
在慢慢的看完了_GC滑迷宫之后,我开始试着做迷宫了,
P1605 迷宫
迷宫的操作其实就是把滑的部分简化,这里直接用到连通块那个dx和dy就行
对于每一个点都进行四联通操作,然后递归的来模拟走的路线,因为是递归求解,每一个方向只走一次,所以路线就不会重复,如果到了终点,我们就把路线+1
代码如下
#include <iostream> #include <cstdio> using namespace std; int n, m, t, ex, ey, ans = 0, bx, by, tx, ty, X, Y; int dx[4] = {0, 0, 1, -1}; int dy[4] = {-1, 1, 0, 0}; bool pos[6][6], map[6][6]; void qaq(int x, int y) { if (x == ex && y == ey) { ans++; return; } else { for (int i = 0; i <= 3; ++i) { X = x + dx[i]; Y = y + dy[i]; if (!pos[X][Y] && map[X][Y] && X<=m && x>0 && y<=n && Y>0) //没被走过而且不是障碍 { pos[x][y] = 1; qaq(X, Y); pos[x][y] = 0; } } } } int main() { scanf("%d%d%d", &n, &m, &t); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) map[i][j] = true; scanf("%d%d%d%d", &bx, &by, &ex, &ey); for (int i = 1; i <= t; ++i) { scanf("%d%d", &tx, &ty); map[tx][ty] = false; //障碍 } qaq(bx, by); printf("%d", ans); return 0; } /* 5 5 0 1 1 2 2 4846 */
这里吧,有一个很坑的点,我因为一开始写的太快了,就忘了加上判边界的情况,所以就只得了80,这也算是一个教训,因为小样例一般比较简单也没什么坑,但是以后写题的时候还是加上更多的判断比较好
接下来我们看一个题
P1141 01迷宫
这个题的题面就很有意思,我是读了好长时间才读懂,关键是吧,他给的样例又非常水,不知道到底应该怎么算,我后来向JYY神仙讨教一番才知道是这么算
这是个例子:
1100 0101 0011 1010
我们看(1,1)这个点吧,它能到达的点数总共有8个,分别是本身,(1,4)(2,1),(2,2),(2,3),(2,4),(3,2),(3,3)
仔细看一看的话,我们就可以用搜索连通块的知识来求这些数量。
这个时候,联系着上面几个已经有的例子,我们就把代码构建的差不多了
这个时候,开心的交上代码,结果看到了这个。。。。。。
我们来看看这个TLE的代码
#include<cstdio> using namespace std; int n,m,i,j,x,y,s,t=1,f[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; int a[1001][1001],b[1001][1001]; char c[1001]; void Dfs(int x,int y) { b[x][y]=t; for(int k=0;k<=3;k++) { int tx=x+f[k][0],ty=y+f[k][1]; if(!(tx<1||tx>n||ty<1||ty>n||b[tx][ty]==t||a[tx][ty]==a[x][y])) { s++; b[tx][ty]=t; Dfs(tx,ty); } } return ; } int main() { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%s",c); for(j=1;j<=n;j++) a[i][j]=c[j-1]; } while(m--) { scanf("%d%d",&x,&y); Dfs(x,y); printf("%d ",s+1); s=0; t++; } return 0; }
当然我不会告诉你这是_GC的代码。。。。。。。
我们仔细研究一下这个代码,会发现这个代码对于每一组数据的处理是读进来算完再输出,当然这个方法好想也好实现,数据不是太变态也问题不大,但是架不住你谷有卡到数据范围的测评点啊!!!!!!!!
这玩意直接爆时间啊!!!!!!!
我们得想一想怎么优化这个东西
啊终于想出来了(@某位大学数学讲师)
我们可以用预处理的方法来解决,首先我们会发现同一个连通块上所有的点能到达的点的数量是一样的,那么就简单了呀,我们按照题目要求,先跑一遍所有的连通块,把这个数值赋给每一个其中的元素,然后他问哪个就输出哪个不就OK了嘛~
#include <iostream> #include <cstring> #include <queue> using namespace std; struct point { int x, y; point(int a, int b) : x(a), y(b) {} }; const int maxn = 1005; char _map[maxn][maxn]; //存储地图 int dx[4] = {0, 0, 1, -1}; int dy[4] = {-1, 1, 0, 0}; int pos[maxn][maxn]; //给搜索过的位置作标记 int n, m, i, j; int ans[maxn * 100] = {0}; //存储第t次搜索的结果 int qaq(int x, int y, int t) { int count = 1; queue<point> q; point p(x, y); q.push(p); while (!q.empty()) { p = q.front(); //cout<<p.x<<" "<<p.y<<endl;测试用的 q.pop(); for (i = 0; i < 4; i++) { int tx = p.x + dx[i]; int ty = p.y + dy[i]; if (tx > 0 && tx <= n && ty > 0 && ty <= n && _map[p.x][p.y] != _map[tx][ty] && pos[tx][ty] == 0) { point p1(tx, ty); q.push(p1); pos[tx][ty] = t; count++; } } } return count; } int main() { cin >> n >> m; for (i = 1; i <= n; i++) cin >> _map[i] + 1; for (int k = 1; k <= m; k++) { int x, y; cin >> x >> y; if (!pos[x][y]) //没有被搜索过,再搜索一遍 { pos[x][y] = k; ans[k] = qaq(x, y, k); cout << ans[k] << endl; } else //被搜索过的话,直接输出结果 { int t = pos[x][y]; ans[m] = ans[t]; cout << ans[m] << endl; } } return 0; } /*有一个仅由数字0与1组成的n×n格迷宫。 若你位于一格0上,那么你可以移动到相邻4格中的某一格1上, 同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。 你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。 1100 0101 0011 1010 */
最后说一句,预处理这玩意真的挺好用的,尤其是多组数据多次询问,简直必备佳品啊!!!!!!!!!