前言: 1.最短路问题和DP的关系:
最短路问题包含DP问题,DP问题可以看做最短路问题的特殊情况。
DP是没有环的最短路问题。
2.不是所有最短路问题都能用bfs来做。
只有所有边权重一样的时候才能用bfs做。
3.几种剪枝
最优性剪枝:当前路径一定可以判断出不如最优解
可行性剪枝:当前路径一定不合法
搜索 DFS 全排列
朴素版:简单递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std ;const int N = 10 ;int n;int path[N];bool flag[N];void dfs (int u) { if (u==n){ for (int i=0 ;i<n;i++){ printf ("%d" ,path[i]); if (i!=n-1 ) printf (" " ); else printf ("\n" ); } } for (int i = 1 ;i<=n;i++) { if (!flag[i]) { path[u] = i; flag[i] = true ; dfs (u+1 ); flag[i] = false ; } } } int main (void ) { cin>>n; dfs (0 ); }
位运算版 :只是将朴素版的标志数组 以整数位运算的方式表示了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;const int N = 10 ;int n;int path[N];void dfs (int u, int state) { if (u == n) { for (int i = 0 ; i < n; i ++ ) printf ("%d " , path[i]); puts ("" ); return ; } for (int i = 0 ; i < n; i ++ ) if (!(state >> i & 1 )) { path[u] = i + 1 ; dfs (u + 1 , state + (1 << i)); } } int main () { scanf ("%d" , &n); dfs (0 , 0 ); return 0 ; }
state存的是当前i位的是否被填了的状态,但是state是一个整数,所以用state第i位是不是零存第i位的状态,查询第i位是不是零就是用!(state >> i & 1)
把具体的值加到path中去了之后,把下一个值递归到下一个dfs循环中,这时候把当前i左移一位加到state中就表示这一位已经被占用了。
n皇后
把不放皇后看成0, 放皇后看成1,所有的情况就成了一个二叉树,每次都是向左(0)遍历,到了终点再回溯到能向右(1)遍历的节点开始向右遍历,是个二叉树的先序遍历算法。 —— acwing-user : Hsezr
这里需要注意,n皇后需要规避三种情况:横线,对角线,反对角线。
可以开辟三个数组,分别存储当前下标状态,是否已经存在皇后了。
横线好说,对角线如何处理呢?
col是Column(列)的缩写,dg是diagonal(对角)的缩写,反对角线为 udg
设 udg 的坐标系方程为y=x+b,则b=y-x。
替换后即 b=u-i,防止出现负数,则加上n,则有b=u+n-i(其实b=n+i-u也可,目的是一个对角线能单独映射)
设 dg 的方程为y=-x+b,b=y+x,替换后 b=i+u
注意:图中行为 n , 列为col。 下面的代码中,我们设置行为 u ,列为 i
全排列做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> using namespace std ;const int N = 20 ;char graph[N][N];bool col[N],dg[N],udg[N]; int n;void dfs (int u) { if (u==n){ for (int i=0 ;i<n;i++){ cout<<graph[i]<<endl; } cout<<endl; return ; } for (int i=0 ;i<n;i++) { if (!col[i]&&!dg[u+i]&&!udg[u+n-i]){ col[i]=dg[u+i]=udg[u+n-i] = true ; graph[u][i] = 'Q' ; dfs (u+1 ); graph[u][i] = '.' ; col[i]=dg[u+i]=udg[u+n-i] = false ; } } } int main (void ) { cin>>n; for (int i=0 ;i<n;i++){ for (int j=0 ;j<n;j++){ graph[i][j] = '.' ; } } dfs (0 ); }
注意:开始我初始化N=10 ,有些样例是过不了的。对角线长度约1.414*10(根号2),经过测试,在1<=n<=9 的情况下, N=16 会WA掉n=9 ,N=17 就能 AC 。
复杂度:n*n!
朴素暴力做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std ;const int N = 20 ;int n;char g[N][N];bool row[N],col[N],dg[N],udg[N]; void dfs (int x,int y, int s) { if ( s>n ) return ; if ( y==n ) { y=0 ; x++; } if (x==n){ if (s==n){ for (int i=0 ;i<n;i++){ puts (g[i]); } puts ("" ); } return ; } g[x][y] = '.' ; dfs (x, y + 1 , s); if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) { row[x] = col[y] = dg[x + y] = udg[x - y + n] = true ; g[x][y] = 'Q' ; dfs (x, y + 1 , s + 1 ); g[x][y] = '.' ; row[x] = col[y] = dg[x + y] = udg[x - y + n] = false ; } } int main (void ) { cin>>n; dfs (0 ,0 ,0 ); }
复杂度:2^(n^2)
为什么第一种写法不用考虑行,而第二种则要考虑行?
第一种是我们已经知道一行只能放一个皇后才这么写,第二种是更朴素的想法一个格子一个格子往下走。
这里你还可以参照,第一种写法的状态参数是if : u==n ,第二种写法则是
1 2 3 4 5 6 7 if ( s>n ) return ;if ( y==n ) { y=0 ; x++; } if (x==n){ if (s==n){ ... }
简单来说:
前者不需要考虑同一行里是否发生重复皇后,后者则需要考虑。
全球变暖 你有一张某海域 NxN 像素的照片,”.”表示海洋、”#”表示陆地,如下所示:
…….
.##….
.##….
….##.
..####.
…###.
…….
其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
…….
…….
…….
…….
….#..
…….
…….
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入描述
第一行包含一个整数 N* (1≤N ≤1000)。
以下 N 行 N 列代表一张海域照片。
照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。、
输出一个整数表示答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> using namespace std;const int N = 1010 ;int n;bool flag;int res;bool vis[N][N];char m[N][N];int h[4 ][2 ] = {{0 ,1 },{1 ,0 },{0 ,-1 },{-1 ,0 }};void dfs (int x,int y) { vis[x][y]= true ; if (m[x][y]=='.' ) return ; bool temp = true ; for (int i=0 ;i<4 ;i++) { int tx = x+h[i][0 ] , ty = y+h[i][1 ]; if (m[tx][ty]=='.' ) temp = false ; if (!vis[tx][ty]) dfs (tx,ty); } if (temp) flag = true ; } int main (void ) { scanf ("%d" ,&n); for (int i=0 ;i<n;i++){ for (int j=0 ;j<n;j++){ cin>>m[i][j]; } } for (int i=0 ;i<n;i++){ for (int j=0 ;j<n;j++){ if (!vis[i][j]&&m[i][j]=='#' ){ flag = false ; dfs (i,j); if (!flag) res++; } } } cout<<res; }
BFS 走迷宫
普通版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <cstring> #include <iostream> #include <algorithm> #include <queue> using namespace std;typedef pair<int , int > PII;const int N = 110 ;int n, m;int g[N][N], d[N][N];int bfs () { queue<PII> q; memset (d, -1 , sizeof d); d[0 ][0 ] = 0 ; q.push ({0 , 0 }); int dx[4 ] = {-1 , 0 , 1 , 0 }, dy[4 ] = {0 , 1 , 0 , -1 }; while (q.size ()) { auto t = q.front (); q.pop (); for (int i = 0 ; i < 4 ; i ++ ) { int x = t.first + dx[i], y = t.second + dy[i]; if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1 ) { d[x][y] = d[t.first][t.second] + 1 ; q.push ({x, y}); } } } return d[n - 1 ][m - 1 ]; } int main () { cin >> n >> m; for (int i = 0 ; i < n; i ++ ) for (int j = 0 ; j < m; j ++ ) cin >> g[i][j]; cout << bfs () << endl; return 0 ; }
手搓队列的朴素版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <cstring> using namespace std ;const int N = 110 ;typedef pair<int ,int > PII;int g[N][N];int d[N][N];int n,m;PII q[N*N]; int bfs () { int hh=0 ,tt=0 ; memset (d,-1 ,sizeof (d)); q[0 ]={0 ,0 }; d[0 ][0 ] = 0 ; int dir[4 ][2 ] = {{0 ,1 },{1 ,0 },{-1 ,0 },{0 ,-1 }}; while (hh<=tt) { auto t = q[hh++]; for (int i=0 ;i<4 ;i++){ int tx = t.first +dir[i][0 ],ty = t.second+dir[i][1 ]; if (tx>=0 &&tx<n&&ty>=0 &&ty<m&&g[tx][ty]==0 &&d[tx][ty]==-1 ){ q[++tt] = {tx,ty}; d[tx][ty] = d[t.first][t.second]+1 ; } } } return d[n-1 ][m-1 ]; } int main (void ) { cin>>n>>m; for (int i=0 ;i<n;i++){ for (int j=0 ;j<m;j++){ scanf ("%d" ,&g[i][j]); } } cout<<bfs (); return 0 ; }
八数码问题
这里搜索需要处理两个点:
1.状态表示。 —— queue队列
2.状态之间的距离如何记录。 —— dist数组
答案:
1.将二维网格降维成一个字符串来表示,如上面1~8的示例,可以用字符串“123x46758” 来表示。
即queue
2.用C++里的哈希表STL : unordered_map<string,int>
最后一个问题:状态转移
①复原状态:将一维字符串还原成二维(至少先从脑子里)
②更新状态:枚举空格上下左右移动后的状态。
③存入队列:移动后把二维结果恢复成字符串,存入bfs队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <queue> #include <algorithm> #include <unordered_map> #include <string> using namespace std ;#define NUM 3 queue<string> q; unordered_map<string,int > d; int bfs (string state) { string end = "12345678x" ; q.push (state); d[state] = 0 ; int direct[4 ][2 ] = {{0 ,1 },{1 ,0 },{0 ,-1 },{-1 ,0 }}; while (q.size ()) { string str = q.front (); q.pop (); if (str == end) return d[str]; int distance = d[str]; int key = str.find ('x' ); int x = key/NUM, y = key%NUM; for (int i=0 ;i<4 ;i++) { int tx = x + direct[i][0 ], ty = y + direct[i][1 ]; if (tx>=0 &&tx<NUM&&ty>=0 &&ty<NUM) { swap (str[key],str[tx*NUM+ty]); if (!d[str]) { d[str] = distance +1 ; q.push (str); } swap (str[key],str[tx*NUM+ty]); } } } return -1 ; } int main (void ) { string start; for (int i=0 ;i<9 ;i++){ char s[2 ]; cin>>s; start += *s; } cout<<bfs (start); return 0 ; }
迷宫 问题描述
这天, 小明在玩迷宫游戏。
迷宫为一个 n x n 的网格图, 小明可以在格子中移动, 左上角为 (1,1)(1,1), 右 下角 (n, n)(n ,n ) 为终点。迷宫中除了可以向上下左右四个方向移动一格以外, 还有 m 个双向传送门可以使用, 传送门可以连接两个任意格子。
假如小明处在格子(x 1,y 1), 同时有一个传送门连接了格子 (x 1,y 1) 和 (x 2,y 2), 那么小明既可以花费 1 的步数向上下左右四个方向之一走一格 (不能 越过边界), 也可以花费 1 的步数通过传送门走到格子 (x 2,y 2) 去。
而对于同一个迷宫, 小明每次进入的初始格子是在这n*×n 个格子中均匀随 机的 (当然运气好可以直接随机到终点), 他想知道从初始格子走到终点的最短 步数的期望值是多少。
输入格式
输入共 1+m1+m 行, 第一行为两个正整数 n, mn ,m 。
后面 mm 行, 每行四个正整数 x1,y1,x2,y2 表示第 ii 个传送门连接的两个格子坐标。
输出格式
输出共一行, 一个浮点数表示答案 (请保留两位小数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <deque> using namespace std;const int N = 25 ;int a[N];int b[N];int vis[N][N];int n;int dis[4 ][2 ] = {{0 ,1 },{1 ,0 },{0 ,-1 },{-1 ,0 }};deque<int > d; bool dfs (int x,int y) { if (x==n-1 &&y==n-1 ){ for (int i=0 ;i<n;i++){ if (a[i]||b[i]) return false ; } return true ; } for (int i=0 ;i<4 ;i++){ int tx = x+dis[i][0 ],ty = y+dis[i][1 ]; if (tx>=0 &&ty>=0 &&tx<n&&ty<n&&!vis[tx][ty]&&(a[ty])&&(b[tx])) { a[ty]--;b[tx]--; vis[tx][ty]=1 ; d.push_back (tx*n+ty); if (dfs (tx,ty)) return true ; a[ty]++;b[tx]++; vis[tx][ty]=0 ; d.pop_back (); } else continue ; } return false ; } int main () { cin>>n; for (int i=0 ;i<n;i++) cin>>a[i]; for (int i=0 ;i<n;i++) cin>>b[i]; d.push_back (0 ); vis[0 ][0 ]=1 ; a[0 ]--;b[0 ]--; if (dfs (0 ,0 )){ for (auto it = d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } } return 0 ; }