文章目录
- 前言
- 例14-1 四阶数独
- 例14-2八皇后
- 例14-3 kkksc03考前临时抱佛脚
- 例14-4 马的遍历
- 例14-5 奇怪的电梯
- 例14-6 Meteor Shower S
- 习题14-1.1 选数
- 例14-1 四阶数独
- 例14-2八皇后
- 例14-3 kkksc03考前临时抱佛脚
- 例14-4 马的遍历
- 例14-5 奇怪的电梯
- 例14-6 Meteor Shower S
- 习题14-1.1 选数
- 习题14-1.2 PERKET
- 习题14-2 迷宫
- 习题14-3 单词接龙
- 习题14-4 单词方阵
- 习题14-5 自然数的拆分问题
- 习题14-6 Lake Counting S
- 习题14-7 填充颜色
- 习题14-1.2 PERKET
- 习题14-2 迷宫
- 习题14-3 单词接龙
- 习题14-4 单词方阵
- 习题14-5 自然数的拆分问题
- 习题14-6 Lake Counting S
- 习题14-7 填充颜色
前言
本文详细讲解了dfs算法和bfs算法的原理以及模板实现,并且通过练习题展示了什么时候使用dfs,什么时候使用bfs
电子版教材链接
:
我通过百度网盘分享的文件:深入浅出程序设计…pdf
链接:https://pan.baidu.com/s/1kmF8wZLnK3Zci7s1ffjRzw
提取码:Ra3Q
复制这段内容打开「百度网盘APP即可获取」
例14-1 四阶数独
前置知识:
dfs模板
void dfs(int k) k表示递归的层数,或者说要填第几个空
{
if(所有空都填完了)
{
判断最优解/记录答案
return;
}
for(枚举这个空能填的选项)
if(这个选项是合法的){
记录下这个空
dfs(k+1)
取消这个空
}
}
本题我们要填16个空,所以我们从第一个空开始填,并且往后填空进行搜索,搜到符合的就继续,不符合的就回溯进行下一个搜索
搜索的流程是,我们定义三个数组,每个数组的0和1状态表示这个数组是否被标记,如果符合:
每一行都填满1到4
每一列都填满1到4
每一个小方格(2x2)是否用过1到4的所有数
我们就填入数字,并且记录下来,去填下一个空
#include <bits/stdc++.h>
using namespace std;
int a[5*5],n = 4*4,ans = 0;
int b1[5][5],b2[5][5],b3[5][5];
void dfs(int x)
{
if(x>n){
ans++;
for(int i = 1;i<=n;i++)
{
printf("%d ",a[i]);
if(i%4 == 0) puts("");
}
puts("");
return;
}
int row = (x-1)/4 +1;
int col = (x-1)%4 + 1;
int block = (row-1) / 2 *2 + (col-1) /2 +1; // 标识当前格子属于哪一个 2×2 小宫格
for(int i = 1;i<=4;i++)
{
if(b1[row][i] == 0 && b2[col][i] == 0 && b3[block][i] == 0) // 最后一个是当前块 block没有使用过数字 i
{
a[x] = i;
b1[row][i] = 1;b2[col][i] = 1;b3[block][i] = 1;
dfs(x+1);
b1[row][i] = 0;b2[col][i] = 0;b3[block][i] = 0;
}
}
}
int main()
{
dfs(1);
printf("%d",ans);
return 0;
}
例14-2八皇后
#include <bits/stdc++.h>
using namespace std;
#define maxn 100
int a[maxn],n,ans = 0;
int b1[maxn],b2[maxn],b3[maxn]; // b1表示列,b2表示右下行斜线,b3表示左上行斜线
void dfs(int x)
{
if(x>n)
{
ans++;
if(ans<=3)
{
for(int i = 1;i<=n;i++)
printf(“%d ”,a[i]);
puts(“”);
}
return;
}
for(int i=1;i<=n;i++)
if(b1[i] == 0 && b2[x+i] ==0 && b3[x-i+15] == 0){ // 由于n<13所以我们这里为了防止x-y小于0,所以我们就扩充为列x-i+15,本质上其实表示的还是x-y
a[x] = i;
b1[i] = 1;b2[x+i] = 1;b3[x-i+15] = 1;
dfs(x+1);
b1[i] = 0;b2[x+i] = 0;b3[x-i+15] = 0;
}
}
int main()
{
scanf(“%d”,&n);
dfs(1);
printf(“%d”,ans);
return 0;
}
例14-3 kkksc03考前临时抱佛脚
每次我们都只需要考虑一科的时间,所以我们要尽量的用完大脑可以左右互用这个性质,把所有的题分为两组,使得左脑做一组,右脑做一组,然后意思就是使得分得的两组分别的耗时总和最为接近(避免有一边大脑是闲置的造成时间的浪费)
再优化一下上面的分析,就是在这些题目中找寻一个子集,使得子集耗时不超过总耗时的一半,说到子集,我们当然可以考虑使用位运算进行枚举,但是最后的结果是2的20次幂,显然是百万级别的,进行了很多不必要的计算,所以我们考虑使用深度优先搜索的方法,搜索是否把题目归入子集,如果是就继续搜索,不是就回溯
#include <bits/stdc++.h>
using namespace std;
int nowtime,maxtime,sum;
int ans,maxdeep;
int s[4],a[21];
void dfs(int x)
{
if(x>maxdeep)
{
maxtime = max(maxtime,nowtime);
return;
}
if(nowtime +a[x] <= sum /2)
{
nowtime += a[x];
dfs(x+1);
nowtime -= a[x];
}
dfs(x+1);
}
int main()
{
cin >> s[0] >> s[1] >> s[2] >> s[3];
for(int i = 0;i<4;i++)
{
nowtime = 0;
maxdeep = s[i];
sum = 0;
for(int j = 1;j<=s[i];j++)
{
cin >> a[j];
sum += a[j];
}
maxtime = 0;
dfs(1);
ans += (sum-maxtime);
}
cout << ans;
return 0;
}
例14-4 马的遍历
前置知识
队列实现广度优先搜索(bfs)
Q.push(初始状态); // 将初始状态入队
while(!Q.empty()){
State u = Q.front(); // 取出队首
Q.pop(); // 出队
for(枚举所有可扩展状态)
if(是合法的)
Q.push(v); //入队
}
广度优先搜索用于解决连通性和最短路等问题,分析每种状态和初始状态的距离,与初始状态越接近的情况就会越优先进行考虑,每个时刻要做的事情就是从上个时刻(阶段)每个状态扩展出新的状态
本题先建立一个结构体数组用于存储扩展的结点,先让起点入队,然后在队列取状态逐个扩展,又因为每个点只被扩展了一次,所以复杂度是O(mn)级别的
#include <bits/stdc++.>
using namespace std;
#define maxn 310
struct coord{
int x,y;
};
queue<coord> Q;
int ans[maxn][maxn];
int walk[8][2] = {{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int main()
{
int n,m,sx,sy;
memset(ans,-1,sizeof(ans));
cin >> n >> m >> sx >> sy;
coord temp = {sx,sy};
Q.push(tmp); // 使起点入队扩展
ans[sx][sy] = 0;
while(!Q.empty()){
coord u = Q.front();
int ux = u.x,uy = u.y;
Q.pop();
for(int k = 0;k<8;k++){
int x = ux+walk[k][0] ,y = uy+walk[k][1];
int d = ans[ux][uy]; // 记录当前的点作为初始点
if(x<1 || x>n || y<1 || y>m || ans[x][y] != -1)
{
continue;
}
ans[x][y] = d+1; // 每次跳一步,距离就是上一层距离 +1
coord tmp = {x,y};
Q.push(tmp);
}
}
for(int i = 1;i<=n;i++,puts(""))
for(int j = 1;j<=m;j++)
printf("%d ",ans[i][j]);
return 0;
}
例14-5 奇怪的电梯
从起点开始,往上或者往下进行扩展,可以到达”按1次按钮的地方“,这些”按1次按钮的地方“再分别往上或者往下进行扩展(前提是在大厦范围内,且没有访问过),就可以到达”按2次按钮的地方“
#include <bits/stdc++.h>
using namespace std;
struct node{
int floor,d;
};
queue<node> Q;
int n,a,b;
int k[1000],vis[1000];
int main()
{
cin >> n >> a >> b;
for(int i = 1;i<=n;i++)
cin >> k[i];
Q.push((node){a,0}); // 入队初始节点
vis[a] = 1;
node now;
while(!Q.empty())
{
now = Q.front();
Q.pop();
if(now.floor == b) break;
for(inb sign = -1;sign<=1;sign +=2){ // 用于表示状态-1和1
int dist = now.floor + k[now.floor] * sign;
if(dist>=1 && dist<=n && vis[dist] == 0){
Q.push((node){dist,now.d+1});
vis[dist] = 1;
}
}
}
if(now.floor == b)
cout << now.d << endl;
else
cout << -1 << endl;
return 0;
}
例14-6 Meteor Shower S
本题由于多出了时间的限制,我们需要单独开一个数组记录一下陨石砸到的时间和位置,如果贝西在这个时间之前通过这个位置,就是合法的,反之就非法的;如果贝西跑到了300x300之外的地方,那么久绝对是安全的,输出最短到这个位置的距离即可
#include <bits/stdc++.h>
using namespace std;
#define maxn 310
struct nihao {
int x, y;
};
queue<nihao> Q;
int ans[maxn][maxn], death[maxn][maxn];
int w[4][2] = {{0,1}, {1,0}, {-1,0}, {0,-1}}; // 右、下、左、上
int main() {
int m, Ans = 1e9;
memset(ans, -1, sizeof(ans));
memset(death, 0x7f, sizeof(death));
cin >> m;
for (int i = 1; i <= m; i++) {
int x, y, t;
cin >> x >> y >> t;
if (x >= 0 && y >= 0 && x < maxn && y < maxn)
death[x][y] = min(death[x][y], t);
for (int k = 0; k < 4; k++) {
int nx = x + w[k][0], ny = y + w[k][1];
if (nx >= 0 && ny >= 0 && nx < maxn && ny < maxn)
death[nx][ny] = min(death[nx][ny], t);
}
}
if (death[0][0] == 0) {
cout << -1 << endl;
return 0;
}
Q.push({0, 0});
ans[0][0] = 0;
while (!Q.empty()) {
nihao u = Q.front(); Q.pop();
int ux = u.x, uy = u.y;
for (int k = 0; k < 4; k++) {
int x = ux + w[k][0], y = uy + w[k][1];
if (x < 0 || y < 0 || x >= maxn || y >= maxn) continue;
if (ans[x][y] != -1) continue;
if (ans[ux][uy] + 1 >= death[x][y]) continue;
ans[x][y] = ans[ux][uy] + 1;
Q.push({x, y});
}
}
for (int i = 0; i < maxn; i++) {
for (int j = 0; j < maxn; j++) {
if (death[i][j] > 1000 && ans[i][j] != -1)
Ans = min(Ans, ans[i][j]);
}
}
if (Ans == 1e9)
cout << -1 << endl;
else
cout << Ans << endl;
return 0;
}
习题14-1.1 选数
对于这道题,我们之前在暴力枚举章节中使用了位运算的方法来选取长度为k的子集再实现质数的判断,现在我们也可以直接使用深搜去搜索满足要求的点并且输出
我们使用了一种隐式回溯的方法避免了定义标记数组,不会造成状态污染
电子版教材链接
:
我通过百度网盘分享的文件:深入浅出程序设计…pdf
链接:https://pan.baidu.com/s/1kmF8wZLnK3Zci7s1ffjRzw
提取码:Ra3Q
复制这段内容打开「百度网盘APP即可获取」
例14-1 四阶数独
前置知识:
dfs模板
void dfs(int k) k表示递归的层数,或者说要填第几个空
{
if(所有空都填完了)
{
判断最优解/记录答案
return;
}
for(枚举这个空能填的选项)
if(这个选项是合法的){
记录下这个空
dfs(k+1)
取消这个空
}
}
本题我们要填16个空,所以我们从第一个空开始填,并且往后填空进行搜索,搜到符合的就继续,不符合的就回溯进行下一个搜索
搜索的流程是,我们定义三个数组,每个数组的0和1状态表示这个数组是否被标记,如果符合:
每一行都填满1到4
每一列都填满1到4
每一个小方格(2x2)是否用过1到4的所有数
我们就填入数字,并且记录下来,去填下一个空
#include <bits/stdc++.h>
using namespace std;
int a[5*5],n = 4*4,ans = 0;
int b1[5][5],b2[5][5],b3[5][5];
void dfs(int x)
{
if(x>n){
ans++;
for(int i = 1;i<=n;i++)
{
printf("%d ",a[i]);
if(i%4 == 0) puts("");
}
puts("");
return;
}
int row = (x-1)/4 +1;
int col = (x-1)%4 + 1;
int block = (row-1) / 2 *2 + (col-1) /2 +1; // 标识当前格子属于哪一个 2×2 小宫格
for(int i = 1;i<=4;i++)
{
if(b1[row][i] == 0 && b2[col][i] == 0 && b3[block][i] == 0) // 最后一个是当前块 block没有使用过数字 i
{
a[x] = i;
b1[row][i] = 1;b2[col][i] = 1;b3[block][i] = 1;
dfs(x+1);
b1[row][i] = 0;b2[col][i] = 0;b3[block][i] = 0;
}
}
}
int main()
{
dfs(1);
printf("%d",ans);
return 0;
}
例14-2八皇后
#include <bits/stdc++.h>
using namespace std;
#define maxn 100
int a[maxn],n,ans = 0;
int b1[maxn],b2[maxn],b3[maxn]; // b1表示列,b2表示右下行斜线,b3表示左上行斜线
void dfs(int x)
{
if(x>n)
{
ans++;
if(ans<=3)
{
for(int i = 1;i<=n;i++)
printf(“%d ”,a[i]);
puts(“”);
}
return;
}
for(int i=1;i<=n;i++)
if(b1[i] == 0 && b2[x+i] ==0 && b3[x-i+15] == 0){ // 由于n<13所以我们这里为了防止x-y小于0,所以我们就扩充为列x-i+15,本质上其实表示的还是x-y
a[x] = i;
b1[i] = 1;b2[x+i] = 1;b3[x-i+15] = 1;
dfs(x+1);
b1[i] = 0;b2[x+i] = 0;b3[x-i+15] = 0;
}
}
int main()
{
scanf(“%d”,&n);
dfs(1);
printf(“%d”,ans);
return 0;
}
例14-3 kkksc03考前临时抱佛脚
每次我们都只需要考虑一科的时间,所以我们要尽量的用完大脑可以左右互用这个性质,把所有的题分为两组,使得左脑做一组,右脑做一组,然后意思就是使得分得的两组分别的耗时总和最为接近(避免有一边大脑是闲置的造成时间的浪费)
再优化一下上面的分析,就是在这些题目中找寻一个子集,使得子集耗时不超过总耗时的一半,说到子集,我们当然可以考虑使用位运算进行枚举,但是最后的结果是2的20次幂,显然是百万级别的,进行了很多不必要的计算,所以我们考虑使用深度优先搜索的方法,搜索是否把题目归入子集,如果是就继续搜索,不是就回溯
#include <bits/stdc++.h>
using namespace std;
int nowtime,maxtime,sum;
int ans,maxdeep;
int s[4],a[21];
void dfs(int x)
{
if(x>maxdeep)
{
maxtime = max(maxtime,nowtime);
return;
}
if(nowtime +a[x] <= sum /2)
{
nowtime += a[x];
dfs(x+1);
nowtime -= a[x];
}
dfs(x+1);
}
int main()
{
cin >> s[0] >> s[1] >> s[2] >> s[3];
for(int i = 0;i<4;i++)
{
nowtime = 0;
maxdeep = s[i];
sum = 0;
for(int j = 1;j<=s[i];j++)
{
cin >> a[j];
sum += a[j];
}
maxtime = 0;
dfs(1);
ans += (sum-maxtime);
}
cout << ans;
return 0;
}
例14-4 马的遍历
前置知识
队列实现广度优先搜索(bfs)
Q.push(初始状态); // 将初始状态入队
while(!Q.empty()){
State u = Q.front(); // 取出队首
Q.pop(); // 出队
for(枚举所有可扩展状态)
if(是合法的)
Q.push(v); //入队
}
广度优先搜索用于解决连通性和最短路等问题,分析每种状态和初始状态的距离,与初始状态越接近的情况就会越优先进行考虑,每个时刻要做的事情就是从上个时刻(阶段)每个状态扩展出新的状态
本题先建立一个结构体数组用于存储扩展的结点,先让起点入队,然后在队列取状态逐个扩展,又因为每个点只被扩展了一次,所以复杂度是O(mn)级别的
#include <bits/stdc++.>
using namespace std;
#define maxn 310
struct coord{
int x,y;
};
queue<coord> Q;
int ans[maxn][maxn];
int walk[8][2] = {{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int main()
{
int n,m,sx,sy;
memset(ans,-1,sizeof(ans));
cin >> n >> m >> sx >> sy;
coord temp = {sx,sy};
Q.push(tmp); // 使起点入队扩展
ans[sx][sy] = 0;
while(!Q.empty()){
coord u = Q.front();
int ux = u.x,uy = u.y;
Q.pop();
for(int k = 0;k<8;k++){
int x = ux+walk[k][0] ,y = uy+walk[k][1];
int d = ans[ux][uy]; // 记录当前的点作为初始点
if(x<1 || x>n || y<1 || y>m || ans[x][y] != -1)
{
continue;
}
ans[x][y] = d+1; // 每次跳一步,距离就是上一层距离 +1
coord tmp = {x,y};
Q.push(tmp);
}
}
for(int i = 1;i<=n;i++,puts(""))
for(int j = 1;j<=m;j++)
printf("%d ",ans[i][j]);
return 0;
}
例14-5 奇怪的电梯
从起点开始,往上或者往下进行扩展,可以到达”按1次按钮的地方“,这些”按1次按钮的地方“再分别往上或者往下进行扩展(前提是在大厦范围内,且没有访问过),就可以到达”按2次按钮的地方“
#include <bits/stdc++.h>
using namespace std;
struct node{
int floor,d;
};
queue<node> Q;
int n,a,b;
int k[1000],vis[1000];
int main()
{
cin >> n >> a >> b;
for(int i = 1;i<=n;i++)
cin >> k[i];
Q.push((node){a,0}); // 入队初始节点
vis[a] = 1;
node now;
while(!Q.empty())
{
now = Q.front();
Q.pop();
if(now.floor == b) break;
for(inb sign = -1;sign<=1;sign +=2){ // 用于表示状态-1和1
int dist = now.floor + k[now.floor] * sign;
if(dist>=1 && dist<=n && vis[dist] == 0){
Q.push((node){dist,now.d+1});
vis[dist] = 1;
}
}
}
if(now.floor == b)
cout << now.d << endl;
else
cout << -1 << endl;
return 0;
}
例14-6 Meteor Shower S
本题由于多出了时间的限制,我们需要单独开一个数组记录一下陨石砸到的时间和位置,如果贝西在这个时间之前通过这个位置,就是合法的,反之就非法的;如果贝西跑到了300x300之外的地方,那么久绝对是安全的,输出最短到这个位置的距离即可
#include <bits/stdc++.h>
using namespace std;
#define maxn 310
struct nihao {
int x, y;
};
queue<nihao> Q;
int ans[maxn][maxn], death[maxn][maxn];
int w[4][2] = {{0,1}, {1,0}, {-1,0}, {0,-1}}; // 右、下、左、上
int main() {
int m, Ans = 1e9;
memset(ans, -1, sizeof(ans));
memset(death, 0x7f, sizeof(death));
cin >> m;
for (int i = 1; i <= m; i++) {
int x, y, t;
cin >> x >> y >> t;
if (x >= 0 && y >= 0 && x < maxn && y < maxn)
death[x][y] = min(death[x][y], t);
for (int k = 0; k < 4; k++) {
int nx = x + w[k][0], ny = y + w[k][1];
if (nx >= 0 && ny >= 0 && nx < maxn && ny < maxn)
death[nx][ny] = min(death[nx][ny], t);
}
}
if (death[0][0] == 0) {
cout << -1 << endl;
return 0;
}
Q.push({0, 0});
ans[0][0] = 0;
while (!Q.empty()) {
nihao u = Q.front(); Q.pop();
int ux = u.x, uy = u.y;
for (int k = 0; k < 4; k++) {
int x = ux + w[k][0], y = uy + w[k][1];
if (x < 0 || y < 0 || x >= maxn || y >= maxn) continue;
if (ans[x][y] != -1) continue;
if (ans[ux][uy] + 1 >= death[x][y]) continue;
ans[x][y] = ans[ux][uy] + 1;
Q.push({x, y});
}
}
for (int i = 0; i < maxn; i++) {
for (int j = 0; j < maxn; j++) {
if (death[i][j] > 1000 && ans[i][j] != -1)
Ans = min(Ans, ans[i][j]);
}
}
if (Ans == 1e9)
cout << -1 << endl;
else
cout << Ans << endl;
return 0;
}
习题14-1.1 选数
对于这道题,我们之前在暴力枚举章节中使用了位运算的方法来选取长度为k的子集再实现质数的判断,现在我们也可以直接使用深搜去搜索满足要求的点并且输出
我们使用了一种隐式回溯的方法避免了定义标记数组,不会造成状态污染
完整实现代码如下:
#include <bits/stdc++.h>
using namespace std;
int n,k,a[25];
long long ans;
bool isPrime(int x)
{
if(x == 1) return 0;
for(int i = 2;i<=sqrt(x);i++)
{
if(x%i == 0) return 0;
}
return 1;
}
// 隐式回溯写法
void dfs1(int m,int sum,int startx) // 其中m表示选择几个数,sum表示此时的和,startx表示从哪个索引开始
{
if(m == k)
{
if(isPrime(sum))
{
ans ++;
}
return ;
}
for(int i = startx;i<n;i++) // 隐式的点在于每次都会更新startx,每次搜索的起点都是不一样的
{
dfs1(m+1,sum+a[i],i+1);
}
}
// 显式回溯写法
int v[30];
void dfs2(int m, int sum) {
if (m == k) {
if (isPrime(sum)) ans++;
return;
}
for (int i = 0; i < n; i++) {
if (!vis[i]) {
// 选择 a[i]
vis[i] = true;
dfs2(m + 1, sum + a[i]);
// 撤销选择 a[i]
vis[i] = false;
}
}
int main()
{
cin >> n >> k;
for(int i = 0;i<n;i++)
cin >> a[i];
dfs1(0,0,0);
cout << ans << endl;
return 0;
}
习题14-1.2 PERKET
本题要找到总酸度和总苦度的最小绝对差,所以我们可以进行深搜来得到答案
#include <bits/stdc++.h>
using namespace std;
int N,s[12],b[12],mi=2000000001;
void dfs(int x,int S,int B)
{
if(x == N)
{
mi = min(mi,abs(S-B));
return ;
}
dfs(x+1,S*s[x],B+b[x]); // 搜索添加的
dfs(x+1,S,B); // 搜索不添加的
}
void dfs(int x) {
for(int i=1;i<=n;i++)
{
if(f[i]==0) //没有查找过的才操作
{
c*=s[i];
y+=b[i];
ans=min(ans,abs(c-y)); //取最小值
f[i]=1; //记录
dfs(x+1);
f[i]=0; //取消记录
c/=s[i]; y-=b[i];
}
}
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d %d",&s[i],&b[i]);
if(N==1)
{
printf("%d",abs(s[0]-b[0]));
return 0;
}
dfs(0,1,0); // 这里要注意S必须为1,如果是0后续乘积就都是0了
printf("%d",mi);
return 0;
}
习题14-2 迷宫
#include <bits/stdc++.h>
using namespace std;
int n, m, t, sx, sy, fx, fy, b, c, a[10][10], ans;
bool temp[10][10];
int w[4][2] = {{0,1}, {1,0}, {-1,0}, {0,-1}}; // 右、下、左、上
void dfs(int x, int y) {
if (x == fx && y == fy) {
ans++;
return;
}
for (int i = 0; i < 4; i++) {
int nx = x + w[i][0];
int ny = y + w[i][1];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m &&
!temp[nx][ny] && a[nx][ny] == -1) {
temp[nx][ny] = 1;
dfs(nx, ny);
temp[nx][ny] = 0;
}
}
}
int main() {
memset(a, -1, sizeof(a));
cin >> n >> m >> t;
cin >> sx >> sy >> fx >> fy;
for (int i = 1; i <= t; i++) {
cin >> b >> c;
a[b][c] = 0;
}
temp[sx][sy] = 1;
dfs(sx, sy);
cout << ans << endl;
return 0;
}
习题14-3 单词接龙
枚举每一个有用的单词,然后判断单词的开头和上个单词的结尾相同,如果相同就继续枚举,没有就回溯
#include <bits/stdc++.h>
using namespace std;
const int N = 66;
int n, ts = 0, ans = 0;
int v[N]; // 限制字符串的使用次数
string s[N];
// 计算 s1 后缀与 s2 前缀的最大重叠长度
int max_overlong(const string& s1, const string& s2) {
int len1 = s1.size(), len2 = s2.size();
int max_overlap = 0;
int max_len = min(len1, len2);
for (int i = 1; i <= max_len; i++) {
if (s1.substr(len1 - i, i) == s2.substr(0, i)) {
max_overlap = i;
}
}
return max_overlap;
}
// 深度优先搜索
void dfs(const string& cur) {
ans = max(ans, ts);
for (int i = 0; i < n; i++) {
if (v[i] < 2) {
int overlap = get_overlap(cur, s[i]);
if (overlap < s[i].size()) { // 有意义的拼接
v[i]++;
ts += s[i].size() - overlap;
dfs(cur + s[i].substr(overlap));
ts -= s[i].size() - overlap;
v[i]--;
}
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> s[i];
char c;
cin >> c;
for (int i = 0; i < n; i++) {
if (s[i][0] == c) {
ts = s[i].size();
v[i]++;
dfs(s[i]);
v[i]--;
}
}
cout << ans << endl;
return 0;
}
习题14-4 单词方阵
本题就是要寻找,在给定的单词方阵中,是否有横竖撇那八列存在单词yizhong
的完整拼写,其它的字母全部变成*
,为了找到目标,我们使用深度优先搜索算法
首先要先确定第一个字母是y,在此基础上我们去查找后面的六个字母
查找六个字母的代码为:
其中i表示是8个方向中的哪个方向
for(int j = 1;j <= 6;j++) {
//以1个方向比较 “izhong”
int nx = x + j*dx[i];
int ny = y + j*dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > n)
{ //判越界
flag = 0;
break;
}
if(cmp[j] != A[nx][ny])
{
flag = 0;
break;
}
}
完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int dx[] = {1, 1, 1, 0, 0, -1,-1,-1 }; //方向数组
const int dy[] = {1, 0,-1, 1,-1, 0 , 1,-1 };
const string cmp = "yizhong";
char A[maxn][maxn],ans[maxn][maxn];
int mark[maxn][maxn],n;
void dfs(int x,int y)
{
for(int i = 0;i<8;i++)
{
int flag = 1;
for(int j = 0;j<6;j++)
{
int nx = x+j*dx[i];
int ny = y+j*dy[i];
if(nx<1 || nx>n || ny<1 || ny >n)
{
flag = 0;
break;
}
if(cmp[j]!=A[nx][ny])
{
flag = 0;
break;
}
}
if(flag = 0) continue;
for(int j = 0;j <= 6;j++)
{ //符合就记录在ans数组里
int nx = x + j*dx[i];
int ny = y + j*dy[i];
ans[nx][ny] = A[nx][ny];
}
}
}
int main()
{
cin >> n;
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
cin >> A[i][j];
}
}
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
if(A[i][j] == 'y') dfs(i,j);//如果发现有y就开始搜索
}
}
for(int i = 1;i<=n;i++)
for(int j = 1;j<=n;j++)
{
if(ans[i][j] == 0) ans[i][j] = '*';//如果没有改动就输出*
cout << ans[i][j];
}
cout << endl;
}
return 0;
}
习题14-5 自然数的拆分问题
#include <bits/stdc++.h>
using namespace std;
int a[11] = {1}, n;
void print(int t) {
for (int i = 1; i < t; i++)
cout << a[i] << "+";
cout << a[t] << endl;
}
void dfs(int s, int t) {
for (int i = a[t - 1]; i <= s; i++) {
a[t] = i;
s -= i;
if (s == 0 && i != n)
print(t);
else
dfs(s, t + 1);
s += i;
}
}
int main() {
cin >> n;
dfs(n, 1);
return 0;
}
习题14-6 Lake Counting S
本题给了一个图,需要我们在图内找到闭合的水潭,并且统计水潭的数量,这题我们可以使用搜索来进行解决
法一:DFS
#include <bits/stdc++.h>
using namespace std;
char a[101][101];
int ans;
int n,m;
void dfs(int x,int y)
{
a[x][y] = '.';
int dx,dy;
for(int i = -1;i<=1;i++)
{
for(int j = -1;j<=1;j++)
{
dx=x+i;
dy=y+j;
if(dx>=0&&dx<=n&&dy>=0&&dy<m&&a[dx][dy]=='W'){
dfs(dx,dy);
}
}
}
}
int main()
{
scanf("%d %d", &n, &m);
for(int i = 0; i < n; i++)
scanf("%s", a[i]);
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(a[i][j] == 'W') {
dfs(i, j);
ans++;
}
}
}
printf("%d\n", ans);
return 0;
}
法二:BFS
#include <bits/stdc++.h>
using namespace std;
queue<int>h;//行的队列
queue<int>p;//列的队列
int n,m;
int ans;
char s[101][101];
void bfs(int x,int y){
s[x][y]='.';
int dx,dy;
for(int i=-1;i<=1;i++){
for(int j=-1;j<=1;j++){
dx=x+i;
dy=y+j;
if(dx>=0&&dx<n&&dy>=0&&dy<m&&s[dx][dy]=='W'){
h.push(dx);
p.push(dy);
}
}
}
}
int main(){
cin >> n >> m;
for(int i=0;i<n;i++){
scanf("%s",s[i]);
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(s[i][j]=='W'){
h.push(i);
p.push(j);
while(!h.empty()){ //如果队列不为空
bfs(h.front(),p.front()); //广搜队列前面的元素
h.pop();p.pop(); //弹出元素
}
ans++;
}
}
}
printf("%d",ans);
return 0;
}
习题14-7 填充颜色
我们首先要找到1的闭合圈,其次把闭合圈内全部换为2,但是边界仍然是1,所以我们正常输入,在外面围一圈0,再从周围搜索就行了
#include<bits/stdc++.h>
using namespace std;
int n,a[35][35];
queue<pair<int,int> >que;
void bfs(int x, int y)
{
int dx[5] = { 0,-1,0,1,0 }, dy[5] = { 0,0,1,0,-1 };
que.push(pair<int, int>(x, y));
while (!que.empty())
{
pair<int, int>t = que.front();
que.pop();
a[t.first][t.second] = 2;
for (int i = 1; i <= 4; i++)
{
int nx = t.first + dx[i];
int ny = t.second + dy[i];
if (nx >= 0 && nx <= n+1 && ny >= 0 && ny <= n+1 && a[nx][ny] == 0) {
que.push({nx,ny});
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
bfs(0,0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cout<<2-a[i][j]<<' ';
cout<<endl;
}
return 0;
}
完整实现代码如下:
#include <bits/stdc++.h>
using namespace std;
int n,k,a[25];
long long ans;
bool isPrime(int x)
{
if(x == 1) return 0;
for(int i = 2;i<=sqrt(x);i++)
{
if(x%i == 0) return 0;
}
return 1;
}
// 隐式回溯写法
void dfs1(int m,int sum,int startx) // 其中m表示选择几个数,sum表示此时的和,startx表示从哪个索引开始
{
if(m == k)
{
if(isPrime(sum))
{
ans ++;
}
return ;
}
for(int i = startx;i<n;i++) // 隐式的点在于每次都会更新startx,每次搜索的起点都是不一样的
{
dfs1(m+1,sum+a[i],i+1);
}
}
// 显式回溯写法
int v[30];
void dfs2(int m, int sum) {
if (m == k) {
if (isPrime(sum)) ans++;
return;
}
for (int i = 0; i < n; i++) {
if (!vis[i]) {
// 选择 a[i]
vis[i] = true;
dfs2(m + 1, sum + a[i]);
// 撤销选择 a[i]
vis[i] = false;
}
}
int main()
{
cin >> n >> k;
for(int i = 0;i<n;i++)
cin >> a[i];
dfs1(0,0,0);
cout << ans << endl;
return 0;
}
习题14-1.2 PERKET
本题要找到总酸度和总苦度的最小绝对差,所以我们可以进行深搜来得到答案
#include <bits/stdc++.h>
using namespace std;
int N,s[12],b[12],mi=2000000001;
void dfs(int x,int S,int B)
{
if(x == N)
{
mi = min(mi,abs(S-B));
return ;
}
dfs(x+1,S*s[x],B+b[x]); // 搜索添加的
dfs(x+1,S,B); // 搜索不添加的
}
void dfs(int x) {
for(int i=1;i<=n;i++)
{
if(f[i]==0) //没有查找过的才操作
{
c*=s[i];
y+=b[i];
ans=min(ans,abs(c-y)); //取最小值
f[i]=1; //记录
dfs(x+1);
f[i]=0; //取消记录
c/=s[i]; y-=b[i];
}
}
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d %d",&s[i],&b[i]);
if(N==1)
{
printf("%d",abs(s[0]-b[0]));
return 0;
}
dfs(0,1,0); // 这里要注意S必须为1,如果是0后续乘积就都是0了
printf("%d",mi);
return 0;
}
习题14-2 迷宫
#include <bits/stdc++.h>
using namespace std;
int n, m, t, sx, sy, fx, fy, b, c, a[10][10], ans;
bool temp[10][10];
int w[4][2] = {{0,1}, {1,0}, {-1,0}, {0,-1}}; // 右、下、左、上
void dfs(int x, int y) {
if (x == fx && y == fy) {
ans++;
return;
}
for (int i = 0; i < 4; i++) {
int nx = x + w[i][0];
int ny = y + w[i][1];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m &&
!temp[nx][ny] && a[nx][ny] == -1) {
temp[nx][ny] = 1;
dfs(nx, ny);
temp[nx][ny] = 0;
}
}
}
int main() {
memset(a, -1, sizeof(a));
cin >> n >> m >> t;
cin >> sx >> sy >> fx >> fy;
for (int i = 1; i <= t; i++) {
cin >> b >> c;
a[b][c] = 0;
}
temp[sx][sy] = 1;
dfs(sx, sy);
cout << ans << endl;
return 0;
}
习题14-3 单词接龙
枚举每一个有用的单词,然后判断单词的开头和上个单词的结尾相同,如果相同就继续枚举,没有就回溯
#include <bits/stdc++.h>
using namespace std;
const int N = 66;
int n, ts = 0, ans = 0;
int v[N]; // 限制字符串的使用次数
string s[N];
// 计算 s1 后缀与 s2 前缀的最大重叠长度
int max_overlong(const string& s1, const string& s2) {
int len1 = s1.size(), len2 = s2.size();
int max_overlap = 0;
int max_len = min(len1, len2);
for (int i = 1; i <= max_len; i++) {
if (s1.substr(len1 - i, i) == s2.substr(0, i)) {
max_overlap = i;
}
}
return max_overlap;
}
// 深度优先搜索
void dfs(const string& cur) {
ans = max(ans, ts);
for (int i = 0; i < n; i++) {
if (v[i] < 2) {
int overlap = get_overlap(cur, s[i]);
if (overlap < s[i].size()) { // 有意义的拼接
v[i]++;
ts += s[i].size() - overlap;
dfs(cur + s[i].substr(overlap));
ts -= s[i].size() - overlap;
v[i]--;
}
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> s[i];
char c;
cin >> c;
for (int i = 0; i < n; i++) {
if (s[i][0] == c) {
ts = s[i].size();
v[i]++;
dfs(s[i]);
v[i]--;
}
}
cout << ans << endl;
return 0;
}
习题14-4 单词方阵
本题就是要寻找,在给定的单词方阵中,是否有横竖撇那八列存在单词yizhong
的完整拼写,其它的字母全部变成*
,为了找到目标,我们使用深度优先搜索算法
首先要先确定第一个字母是y,在此基础上我们去查找后面的六个字母
查找六个字母的代码为:
其中i表示是8个方向中的哪个方向
for(int j = 1;j <= 6;j++) {
//以1个方向比较 “izhong”
int nx = x + j*dx[i];
int ny = y + j*dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > n)
{ //判越界
flag = 0;
break;
}
if(cmp[j] != A[nx][ny])
{
flag = 0;
break;
}
}
完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int dx[] = {1, 1, 1, 0, 0, -1,-1,-1 }; //方向数组
const int dy[] = {1, 0,-1, 1,-1, 0 , 1,-1 };
const string cmp = "yizhong";
char A[maxn][maxn],ans[maxn][maxn];
int mark[maxn][maxn],n;
void dfs(int x,int y)
{
for(int i = 0;i<8;i++)
{
int flag = 1;
for(int j = 0;j<6;j++)
{
int nx = x+j*dx[i];
int ny = y+j*dy[i];
if(nx<1 || nx>n || ny<1 || ny >n)
{
flag = 0;
break;
}
if(cmp[j]!=A[nx][ny])
{
flag = 0;
break;
}
}
if(flag = 0) continue;
for(int j = 0;j <= 6;j++)
{ //符合就记录在ans数组里
int nx = x + j*dx[i];
int ny = y + j*dy[i];
ans[nx][ny] = A[nx][ny];
}
}
}
int main()
{
cin >> n;
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
cin >> A[i][j];
}
}
for(int i = 1;i <= n;i++) {
for(int j = 1;j <= n;j++) {
if(A[i][j] == 'y') dfs(i,j);//如果发现有y就开始搜索
}
}
for(int i = 1;i<=n;i++)
for(int j = 1;j<=n;j++)
{
if(ans[i][j] == 0) ans[i][j] = '*';//如果没有改动就输出*
cout << ans[i][j];
}
cout << endl;
}
return 0;
}
习题14-5 自然数的拆分问题
#include <bits/stdc++.h>
using namespace std;
int a[11] = {1}, n;
void print(int t) {
for (int i = 1; i < t; i++)
cout << a[i] << "+";
cout << a[t] << endl;
}
void dfs(int s, int t) {
for (int i = a[t - 1]; i <= s; i++) {
a[t] = i;
s -= i;
if (s == 0 && i != n)
print(t);
else
dfs(s, t + 1);
s += i;
}
}
int main() {
cin >> n;
dfs(n, 1);
return 0;
}
习题14-6 Lake Counting S
本题给了一个图,需要我们在图内找到闭合的水潭,并且统计水潭的数量,这题我们可以使用搜索来进行解决
法一:DFS
#include <bits/stdc++.h>
using namespace std;
char a[101][101];
int ans;
int n,m;
void dfs(int x,int y)
{
a[x][y] = '.';
int dx,dy;
for(int i = -1;i<=1;i++)
{
for(int j = -1;j<=1;j++)
{
dx=x+i;
dy=y+j;
if(dx>=0&&dx<=n&&dy>=0&&dy<m&&a[dx][dy]=='W'){
dfs(dx,dy);
}
}
}
}
int main()
{
scanf("%d %d", &n, &m);
for(int i = 0; i < n; i++)
scanf("%s", a[i]);
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(a[i][j] == 'W') {
dfs(i, j);
ans++;
}
}
}
printf("%d\n", ans);
return 0;
}
法二:BFS
#include <bits/stdc++.h>
using namespace std;
queue<int>h;//行的队列
queue<int>p;//列的队列
int n,m;
int ans;
char s[101][101];
void bfs(int x,int y){
s[x][y]='.';
int dx,dy;
for(int i=-1;i<=1;i++){
for(int j=-1;j<=1;j++){
dx=x+i;
dy=y+j;
if(dx>=0&&dx<n&&dy>=0&&dy<m&&s[dx][dy]=='W'){
h.push(dx);
p.push(dy);
}
}
}
}
int main(){
cin >> n >> m;
for(int i=0;i<n;i++){
scanf("%s",s[i]);
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(s[i][j]=='W'){
h.push(i);
p.push(j);
while(!h.empty()){ //如果队列不为空
bfs(h.front(),p.front()); //广搜队列前面的元素
h.pop();p.pop(); //弹出元素
}
ans++;
}
}
}
printf("%d",ans);
return 0;
}
习题14-7 填充颜色
我们首先要找到1的闭合圈,其次把闭合圈内全部换为2,但是边界仍然是1,所以我们正常输入,在外面围一圈0,再从周围搜索就行了
#include<bits/stdc++.h>
using namespace std;
int n,a[35][35];
queue<pair<int,int> >que;
void bfs(int x, int y)
{
int dx[5] = { 0,-1,0,1,0 }, dy[5] = { 0,0,1,0,-1 };
que.push(pair<int, int>(x, y));
while (!que.empty())
{
pair<int, int>t = que.front();
que.pop();
a[t.first][t.second] = 2;
for (int i = 1; i <= 4; i++)
{
int nx = t.first + dx[i];
int ny = t.second + dy[i];
if (nx >= 0 && nx <= n+1 && ny >= 0 && ny <= n+1 && a[nx][ny] == 0) {
que.push({nx,ny});
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
bfs(0,0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cout<<2-a[i][j]<<' ';
cout<<endl;
}
return 0;
}