DFS,就是暴力搜索,去枚举所有的方案,每个DFS都会对应于一个搜索树,我们再考虑DFS问题的时候,首先要考虑的就是顺序,用一个怎么样的顺序能得到所有的方案
文章目录
快速入门
排列数字
题目描述
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1 <= n <= 7
题解
之前说过, 每个DFS都会对应一个搜索树, 我们要想一个顺序能够枚举到所有的方案, 从我们所画的这个树来看, 实际上就是暴力枚举每个位置上能放的数字。 每到一层我都会确定一个位置上的数, 当所有位置都被放上数字的时候, 就意味着这条分支已经末尾了,这个时候我就要开始准备回溯, 也就是从1 2 3
的状态恢复到1 2 空
, 可以理解为你进来的时候是什么样子, 你出去的时候就是什么样子。
这里举一个实际的例子,当周末你爸妈出去,留你一个人在家,走之前家里是干干净净的,如果说回来家里乱乱糟糟的,结果可想而知。为了不要挨打,你要在爸妈回来之前,把屋子收拾好,他们走之前是什么样子,回来就是什么样子的,这个过程实际上就是回溯的过程,下面让我们来看一看用代码如何实现。
代码实现
#include<iostream>
using namespace std;
const int N = 10;
bool st[N];//全排列中是不允许数字重复的,所以用一个bool数组来记录每个数是否被用过
int path[N];//用来记录结果
int n;
void dfs(int u){
//如果说我们到达最后一层,说明我们找到了一组方案
if(u == n){
for(int i = 0;i<n;i++){
cout<<path[i]<<" ";
}
cout<<endl;
}
//向该位置上添加数字
for(int i = 1;i<=n;i++){
if(!st[i]){
//如果说这个数在这条路径上没有用过,说明这个位置上可以用这个树来进行填充
st[i] = true;
path[u] = i;
dfs(u+1);
//恢复现场,我进入的时候是怎么样的,出去的时候就要是怎么样
path[u] = 0;
st[i] = false;
}
}
}
int main(){
cin>>n;
dfs(0);
return 0 ;
}
N 皇后
题目描述
n− 皇后问题是指将 n 个皇后放在n × n
的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n。
输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。
其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BahXtRJZ-1641720141168)(https://g.yuque.com/gr/latex?1%E2%89%A4n%E2%89%A49#card=math&code=1%E2%89%A4n%E2%89%A49&id=51fa6594)]
思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9V7bAGR-1641720141169)(https://cdn.jsdelivr.net/gh/freedom0123/img/N%E7%9A%87%E5%90%8E.png#id=JpXI9&originHeight=981&originWidth=1892&originalType=binary&ratio=1&status=done&style=none)]
这个DFS所对应的树就是这样的, 从行开始枚举, 如左图, 如果说这个皇后放在这个位置上, 然后考虑下一行, 看一下, 在上一行的基础基础之上, 当前这一行中那些位置能够放皇后。 这里就会涉及到一个问题, 如何判断当前位置能不能放皇后呢, 根据题意,列,行, 正对角线, 反对角线上只能有一个皇后, 但是因为我们是按行开始枚举的, 所以说行中就不能有重复的皇后, 直接pass。 现在只剩下了列, 正对角线, 反对角线, 通过数学推导, 我们可以很容易得到两个对角线的方程, 通过方程我们知道, 这一条线上的所有点的横纵坐标加起来都对应与一个值, 我们可以通过这个映射值来判断当前这条线上是否有元素。 但是由于i-u 有可能会越界, 所以加一个偏移量, 可以这样理解, 这条线上所有点的横纵坐标都是相同的, 当他们同时加上一个n, 他们仍然是相同的。
代码描述
#include<iostream>
#include<cstring>
using namespace std;
const int N = 10;
char path[N][N];
bool col[N],dg[N*2],udg[N*2];
int n;
void dfs(int u){
if(u == n){
for(int i = 0 ;i < n; i++){
cout<<path[i]<<endl;
}
cout<<endl;
return;
}
for(int i = 0 ; i < n; i++){
if(!dg[i+u] && !udg[i-u+n] && !col[i]){
col[i] = dg[i+u] = udg[i-u+n] = true;
path[u][i] = 'Q';
dfs(u+1);
col[i] = dg[i+u] = udg[i-u+n] = false;
path[u][i] = '.';
}
}
}
int main(){
cin>>n;
for(int i = 0 ;i < n; i++){
for(int j = 0;j < n; j++){
path[i][j] = '.';
}
}
dfs(0);
return 0;
}
连通块模型
迷宫
题目描述
一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n∗n
的格点组成,每个格点只有2种状态,.
和#
,前者表示可以通行后者表示不能通行。同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。如果起点或者终点有一个不能通行(为#),则看成无法办到。
注意:A、B不一定是两个不同的点。
输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。
每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。
接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。
再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。
注意到 ha,la,hb,lb 全部是从 0 开始计数的。
输出格式
k行,每行输出对应一个输入。
能办到则输出“YES”,否则输出“NO”。
数据范围
1≤n≤100
思路
![BEED9F43-C632-4E18-BEC8-3DFE5D6CE611.jpeg](https://img-blog.csdnimg.cn/img_convert/8e9843b78d402f4b9609de70c5e2c1e0.png#clientId=u546c988a-3854-4&from=ui&id=ucd16d9d5&margin=[object Object]&name=BEED9F43-C632-4E18-BEC8-3DFE5D6CE611.jpeg&originHeight=626&originWidth=1537&originalType=binary&ratio=1&size=313429&status=done&style=none&taskId=u8a82f494-50c0-4812-9f14-9b975ca8f60)
代码描述
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
char path[N][N];
bool st[N][N];
int k;
int n;
int xa,ya,xb,yb;
int dx[4] = {-1,1,0,0};
int dy[4] = {0,0,-1,1};
bool dfs(int x,int y){
if (path[x][y] == '#') return false;
if (x == xb && y == yb) return true;
st[x][y] = true;
for (int i = 0; i < 4; i ++ ){
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n) continue;
if (st[a][b]) continue;
if (dfs(a, b)) return true;
}
return false;
}
int main(){
cin>>k;
while(k--){
cin>>n;
for(int i = 0;i<n;i++){
scanf("%s",path[i]);
}
scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
memset(st, 0, sizeof st);
if(dfs(xa,ya)){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
return 0;
}
红与黑
题目描述
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
输入包括多个数据集合。
每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。
在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
思路详解
每个dfs都会对应一棵树,而树都会有一个根节点,相当于这个棋盘中的起始位置,而这个节点都会有四个分支,相当于上下左右方向所到达的点,所以和上一题的树是一样的。本质上就是求这个连通块的大小
代码
#include<cstring>
#include<iostream>
using namespace std;
const int N = 30;
bool st[N][N];
char path[N][N];
int n ,m;
int dx[4] = {-1,1,0,0};
int dy[4] = {0,0,1,-1};
int dfs(int x,int y){
int cnt =1;
st[x][y] = true;
for(int i = 0;i<4;i++){
int a = x+dx[i];
int b = y+dy[i];
if(a<0 || a>=n || b<0 ||b>=m) continue;
if(st[a][b]) continue;
if(path[a][b]!='.') continue;
cnt+=dfs(a,b);
}
return cnt;
}
int main(){
while(cin>>m>>n , n||m){
for(int i = 0;i<n;i++){
cin>>path[i];
}
int x ,y;
for(int i = 0;i<n;i++){
for(int j = 0;j<m;j++){
if(path[i][j]=='@'){
x = i;
y = j;
}
}
}
memset(st,0,sizeof st);
cout<<dfs(x,y)<<endl;
}
return 0;
}
搜索顺序
马走日
题目描述
马在中国象棋以日字形规则移动。
请编写一段程序,给定n∗m
大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。
输入格式
第一行为整数 T,表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。
输出格式
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKb7ndLE-1641720141172)(https://g.yuque.com/gr/latex?1%E2%89%A4T%E2%89%A49%2C%0A%0A1%E2%89%A4m%2Cn%E2%89%A49%2C%0A%0A0%E2%89%A4x%E2%89%A4n%E2%88%921%2C%0A%0A0%E2%89%A4y%E2%89%A4m%E2%88%921#card=math&code=1%E2%89%A4T%E2%89%A49%2C%0A%0A1%E2%89%A4m%2Cn%E2%89%A49%2C%0A%0A0%E2%89%A4x%E2%89%A4n%E2%88%921%2C%0A%0A0%E2%89%A4y%E2%89%A4m%E2%88%921&id=4eb1106c)]
输入样例
1
5 4 0 0
输出样例
32
#include<iostream>
#include<cstring>
using namespace std;
const int N = 10;
int n,m,x,y,ans;
bool st[N][N];
int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
void dfs(int x,int y,int cnt){
if(cnt == n*m){
ans++;
return;
}
st[x][y] = true;
for(int i = 0;i < 8;i++){
int a = dx[i]+x;
int b = dy[i]+y;
if(a < 0 || a>=n || b < 0 || b>=m) continue;
if(st[a][b]) continue;
dfs(a,b,cnt+1);
}
st[x][y] = false;
}
int main(){
int t;
cin>>t;
while(t--){
memset(st,0,sizeof st);
ans = 0;
cin>>n>>m>>x>>y;
dfs(x,y,1);
cout<<ans<<endl;
}
return 0;
}
单词接龙
分成互质组
题意描述
给定 n 个正整数,将它们分组,使得每组中任意两个数互质。
至少要分成多少个组?
输入格式
第一行是一个正整数 n。
第二行是 n 个不大于10000的正整数。
输出格式
一个正整数,即最少需要的组数。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdEFdAm5-1641720141173)(https://g.yuque.com/gr/latex?1%E2%89%A4n%E2%89%A410#card=math&code=1%E2%89%A4n%E2%89%A410&id=126e38ae)]
输入样例:
6
14 20 33 117 143 175
输出样例:
3
思路
代码说明
#include <iostream>
#include <vector>
using namespace std;
const int N = 10;
int n;
int a[N]; // 存数字
int ans=N, len; // ans:全局答案 len:当前开的组数
vector<int> g[N]; // 每一组
int gcd(int x,int y)
{
return y ? gcd(y, x % y) : x;
}
bool check(int u,int c) // u:当前组,c:组编号,判断当前数能否放到这一组中
{
for(int i=0;i<g[c].size();i++)
if(gcd(g[c][i], u) > 1) return false;
return true;
}
void dfs(int u)
{
if(u==n){ // 处理完所有数字了
ans = min(ans,len);
return ;
}
// 不开新组
for(int i = 0;i < len;i++){
if(check(a[u], i)){
g[i].push_back(a[u]);
dfs(u+1);
g[i].pop_back(); // 恢复现场
}
}
// 开新组
g[len++].push_back(a[u]);
dfs(u + 1);
g[--len].pop_back();
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
dfs(0); // 从下标为0的数字开始搜
cout<<ans<<endl;
return 0;
}
剪枝
常用技巧
- 优先搜索分支较少的分支
- 不重复搜素:不会出现重复的结果
- 可行性剪枝:如果说当前方案不可行,就没有必要向下走
- 最优性剪枝:如果说当前分支已经比不上最优解了,就没有必要向下走了
小猫爬山
题目描述
翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。
经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。
翰翰和达达只好花钱让它们坐索道下山。
索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。
当然,每辆缆车上的小猫的重量之和不能超过 W。
每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?
输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。
第 2…N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。
输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQ12pN2M-1641720141174)(https://g.yuque.com/gr/latex?1%E2%89%A4N%E2%89%A418%2C%0A1%E2%89%A4Ci%E2%89%A4W%E2%89%A4108#card=math&code=1%E2%89%A4N%E2%89%A418%2C%0A1%E2%89%A4Ci%E2%89%A4W%E2%89%A4108&id=b72cc05b)]
代码
import java.io.*;
import java.util.*;
import java.util.Collections;
public class Main {
static int N = 20;
static int n,m;
static int[] w = new int[N];
static int[] s = new int[N];
static String[] res = new String[2*N];
static int ans = N;
public static void reverse(){
int i = 0;
int j = n-1;
while(i <= j){
int t = w[i];
w[i] = w[j];
w[j] = t;
i++;
j--;
}
}
public static void dfs(int u,int k){
if(k >= ans){
return;
}
if(u == n){
ans = k;
return;
}
for(int i = 0;i < k;i++){
if(s[i] + w[u] <= m){
s[i] += w[u];
dfs(u+1,k);
s[i] -= w[u];
}
}
s[k] = w[u];
dfs(u+1,k+1);
s[k] = 0;
}
public static void main(String[] args) throws IOException {
BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
res = cin.readLine().split(" ");
n = Integer.parseInt(res[0]);
m = Integer.parseInt(res[1]);
for(int i = 0; i < n ;i++){
w[i] = Integer.parseInt(cin.readLine());
}
Arrays.sort(w,0,n);
reverse();
dfs(0,0);
System.out.println(ans);
cin.close();
}
}
数独
题意描述
数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。
请编写一个程序填写数独。
输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。
每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。
您可以假设输入中的每个谜题都只有一个解决方案。
文件结尾处为包含单词 end 的单行,表示输入结束。
输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。
思路
对于DFS来说, 最重要的是搜索的顺序, 要能够枚举到所有的方案, 这个题对应的一个DFS搜索顺序就是这样的,
- 每次任意选可以填的位置
- 枚举这个位置的所有可选方案
- 递归下一层
确定完搜索顺序之后, 就该考虑了, 在搜索的时候, 这个位置上能放哪个元素,根据数组本身的特点,这个位置上能够放的元素一定是这一行,这一列,这一个九宫格子中没有 出现过的。如果说集合来表示这种关系的话, 实际上就是这三个集合的一个交集。
这里我们使用一个二进制数来表示这一行中哪个元素没有被用过,如果这个二进制数的第 i 位是 1 ,就表示当前这一行 / 列 / 九宫格上,i 没有被用过,有 n 个 1 就表示这个位置上有几种选择( 这个操作可以使用 lowbit
很容易算出来)。
下面我们来举一个例子 0 1 0 1 0 0 1 0 1
就表示当前 这行 / 列 / 九宫格 上,**1 3 6 8**
可以使用, 这里要注意的是, 二进制位是0 ~ 8
,我们要映射为 1 ~ 9
, 求交集就是将这三个数进行 &
运算即可
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 9;
int row[N],col[N],cell[3][3];
int one[1<<N];//每个数的二进制表示中有多少个1
int map[1<<N];//快速求出 2 的幂
char str[100];
inline int lowbit(int x){
return x&-x;
}
void init(){
for(int i = 0;i < N;i++) row[i] = col[i] = (1 << N) - 1;
for(int i = 0; i < 3;i++){
for(int j = 0;j < 3;j++){
cell[i][j] = (1<<N)-1;
}
}
}
inline int get(int x,int y){
return row[x] & col[y] & cell[x/3][y/3];
}
bool dfs(int cnt){
if(!cnt) return true;
// 寻找可用方案最小的横纵坐标
int minv = 10;
int x,y;//可选方案最小的横纵坐标
for(int i = 0;i < N ;i++){
for(int j = 0; j < N;j++){
if(str[i*9 + j] == '.'){
int t = one[get(i,j)];
if(t < minv){
x = i;
y = j;
minv = t;
}
}
}
}
for(int i = get(x,y);i != 0; i -= lowbit(i)){
int t = map[lowbit(i)];
row[x] -= 1<<t;
col[y] -= 1<<t;
cell[x / 3][y /3] -= 1<<t;
str[x * 9 +y] = '1' + t;
if(dfs(cnt-1)) return true;
row[x] += 1<<t;
col[y] += 1<<t;
cell[x / 3][y /3] += 1<<t;
str[x * 9 +y] = '.';
}
return false;
}
int main(){
for(int i = 0 ;i < N ;i++) map[1<<i] = i;
for(int i = 0; i< 1<<N;i++) {
int s = 0;
for (int j = i; j; j -= lowbit(j)) {
s++;
}
one[i] = s;//i 的二进制表示中一共有多少个1
}
while(cin>>str,str[0] != 'e') {
init();
int cnt = 0;
for(int i = 0 , k = 0; i < N;i++){
for(int j = 0; j < N ;j++,k++){
if(str[k]!='.'){
int t = str[k]-'1';
row[i] -= 1<<t;
col[j] -= 1<<t;
cell[i/3][j/3] -= 1<<t;
}else{
cnt++;
}
}
}
dfs(cnt);
cout<<str<<endl;
}
return 0;
}
木棍
题意描述
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
输入格式
输入包含多组数据,每组数据包括两行。
第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。
输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
数据范围
数据保证每一节木棍的长度均不大于 50。
输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例
6
5
思路详解
代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 70;
int n,length,sum;
int w[N];
bool st[N];
bool dfs(int u,int curlen,int start){
if(u * length == sum) return true;
if(curlen == length) return dfs(u+1,0,0);
for(int i = start;i < n;i++){
if(st[i]) continue;
if(curlen + w[i] > length) continue;
st[i] = true;
if (dfs(u, curlen + w[i], i + 1)) return true;
st[i] = false;
if (!curlen || curlen + w[i] == length) return false;
int j = i;
while(j < n && w[j] == w[i]) j++;
i = j-1;
}
return false;
}
int main(){
while(cin>>n,n){
memset(st,0,sizeof st);
sum = 0;
for(int i = 0;i < n; i++){
cin>>w[i];
sum+=w[i];
}
sort(w,w+n);
reverse(w,w+n);
length = 1;
for(;length<=sum;length++){
if(sum % length == 0 && dfs(0,0,0)){
cout<<length<<endl;
break;
}
}
}
return 0;
}
迭代加深
加成序列
题目描述
满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为“加成序列”:
X[1]=1
X[m]=n
X[1]<X[2]<…<X[m−1]<X[m]
对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。
你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的“加成序列”。
如果有多个满足要求的答案,只需要找出任意一个可行解。
输入格式
输入包含多组测试用例。
每组测试用例占据一行,包含一个整数 n。
当输入为单行的 0 时,表示输入结束。
输出格式
对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。
每个输出占一行。
数据范围
1≤n≤100
输入样例:
5
7
12
15
77
0
输出样例:
1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
1 2 4 8 9 17 34 68 77
代码描述
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
int n,depth;
int path[N];
bool st[N];
bool dfs(int u,int k){
if(u == k) return path[u-1] == n;
memset(st,0,sizeof st);
for(int i = u-1;i >= 0; i--){
for(int j = i;j>=0;j--){
int tem = path[i] + path[j];
if(tem > n || tem <= path[u-1] || st[tem]) continue;
st[tem] = true;
path[u] = tem;
if(dfs(u+1,k)) return true;
}
}
return false;
}
int main(){
while(cin>>n,n){
path[0] = 1;
depth = 1;
while(!dfs(1,depth)) depth++;
for(int i = 0 ;i < depth; i++) cout<<path[i]<<' ';
cout<<endl;
}
return 0;
}
双向DFS
送礼物
题目描述
达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]。
达达的力气很大,他一次可以搬动重量之和不超过 W 的任意多个物品。
达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。
输入格式
第一行两个整数,分别代表 W 和 N。
以后 N 行,每行一个正整数表示 G[i]。
输出格式
仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhFjMweZ-1641720141175)(https://g.yuque.com/gr/latex?1%E2%89%A4N%E2%89%A446%2C%0A1%E2%89%A4W%2CG%5Bi%5D%E2%89%A4%202%20%5E31%5E-1#card=math&code=1%E2%89%A4N%E2%89%A446%2C%0A1%E2%89%A4W%2CG%5Bi%5D%E2%89%A4%202%20%5E31%5E-1&id=d0c0c33f)]
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1<<25;
int max_w,n,k;
int cnt = 1;
int ans;//答案
int g[50];
int weight[N];
void dfs1(int u ,int sum){
if(u == k) {
weight[cnt++] = sum;
return;
}
dfs1(u+1,sum);
if((long long)sum + g[u] <= max_w){
dfs1(u+1,sum+g[u]);
}
}
void dfs2(int u ,int sum){
if(u == n){
int l = 0, r = cnt-1;
while(l < r){
int mid = l + r + 1 >>1;
if((long long)weight[mid] +sum <= max_w){
l = mid;
}else{
r = mid -1;
}
}
ans = max(ans,weight[l] + sum);
return;
}
dfs2(u+1,sum);
if((long long)sum+g[u]<=max_w){
dfs2(u+1,sum+g[u]);
}
}
int main(){
cin>>max_w>>n;
for(int i = 0 ;i < n; i++) cin>>g[i];
sort(g,g+n);
reverse(g,g+n);
k = n /2 +2;//将前k个物品的重量打一个表
dfs1(0,0);
sort(weight,weight+cnt);
cnt = unique(weight,weight+cnt) - weight;
dfs2(k,0);
cout<<ans<<endl;
return 0;
}
IDA*
排书
题目描述
给定 n 本书,编号为 1∼n。
在初始状态下,书是任意排列的。
在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。
我们的目标状态是把书按照 1∼n 的顺序依次排列。
求最少需要多少次操作。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据包含两行,第一行为整数 n,表示书的数量。
第二行为 n 个整数,表示 1∼n 的一种任意排列。
同行数之间用空格隔开。
输出格式
每组数据输出一个最少操作次数。
如果最少操作次数大于或等于 5 次,则输出 5 or more。
每个结果占一行。
数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zyS2m4E4-1641720141176)(https://g.yuque.com/gr/latex?1%E2%89%A4n%E2%89%A415#card=math&code=1%E2%89%A4n%E2%89%A415&id=a7a58ac0)]
思路详解
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 15;
int T,n;
int q[N];
int w[5][N];
int f(){
int total = 0;
for(int i = 0 ;i < n-1; i ++){
if(q[i] + 1 != q[i+1]) total++;
}
return (total + 2) / 3;
}
bool check(){
for(int i = 0 ;i < n; i++){
if(q[i]!=i+1){
return false;
}
}
return true;
}
bool dfs(int u ,int max_depth){
if(u + f() > max_depth) return false;
if(check()) return true;
for(int len = 1; len <= n; len++){
for(int l = 0; l+len-1 <n;l++){
int r = l+len-1;
for (int k = r + 1; k < n; k ++ )
{
memcpy(w[u], q, sizeof q);
int x, y;
for (x = r + 1, y = l; x <= k; x ++, y ++ ) q[y] = w[u][x];
for (x = l; x <= r; x ++, y ++ ) q[y] = w[u][x];
if (dfs(u+ 1, max_depth)) return true;
memcpy(q, w[u], sizeof q);
}
}
}
return false;
}
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i = 0 ;i < n; i++) cin>>q[i];
int depth = 0;
while(depth <5 && !dfs(0,depth)) depth++;
if(depth >= 5) cout<<"5 or more"<<endl;
else cout<<depth<<endl;
}
return 0;
}
回转游戏
题意描述
如下图所示,有一个 # 形的棋盘,上面有 1,2,3 三种数字各 8 个。
给定 8 种操作,分别为图中的 A∼H。
这些操作会按照图中字母和箭头所指明的方向,把一条长为 7 的序列循环移动 1 个单位。
例如下图最左边的 # 形棋盘执行操作 A 后,会变为下图中间的 # 形棋盘,再执行操作 C 后会变成下图最右边的 # 形棋盘。
给定一个初始状态,请使用最少的操作次数,使 # 形棋盘最中间的 8 个格子里的数字相同。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6hmPMG7k-1641720141177)(https://cdn.jsdelivr.net/gh/freedom0123/img/Acwing181.jpg#id=So3KE&originHeight=200&originWidth=528&originalType=binary&ratio=1&status=done&style=none)]
输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 24 个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。
输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。
当输入只包含一个 0 的行时,表示输入终止。
输出格式
每个测试用例输出占两行。
第一行包含所有移动步骤,每步移动用大写字母 A∼H 中的一个表示,字母之间没有空格,如果不需要移动则输出 No moves needed。
第二行包含一个整数,表示移动完成后,中间 8 个格子里的数字。
如果有多种方案,则输出字典序最小的解决方案。
输入样例:
1 1 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3
1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3
0
输出样例:
AC
2
DDHH
2
思路
代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 25;
int path[100];
int q[N];
int option[8][8] = {
{0, 2, 6, 11, 15, 20, 22},
{1, 3, 8, 12, 17, 21, 23},
{10, 9, 8, 7, 6, 5, 4},
{19, 18, 17, 16, 15, 14, 13},
{23, 21, 17, 12, 8, 3, 1},
{22, 20, 15, 11, 6, 2, 0},
{13, 14, 15, 16, 17, 18, 19},
{4, 5, 6, 7, 8, 9, 10}
};
int opposite[8] = {5,4,7,6,1,0,3,2};//每个操作的逆过程
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};//中间的8个格子
int f(){
int sum[4];
memset(sum,0,sizeof sum);
for(int i = 0 ;i < 8;i++) sum[q[center[i]]]++;
int s = 0;
for(int i = 0 ;i < 4; i++) s = max(s,sum[i]);
return 8 - s;
}
bool check(){
int st = q[center[0]];
for(int i = 0 ;i < 8;i++) {
if(q[center[i]] != st){
return false;
}
}
return true;
}
void action(int x)
{
int t = q[option[x][0]];
for (int i = 0; i < 6; i ++ ) q[option[x][i]] = q[option[x][i + 1]];
q[option[x][6]] = t;
}
bool dfs(int u,int max_depth,int last){
if(u + f() > max_depth) return false;
if(check()) return true;
for(int i = 0 ;i < 8 ; i++){
if(opposite[i] == last) continue;
action(i);
path[u] = i;
if(dfs(u+1,max_depth,i)) return true;
action(opposite[i]);
}
return false;
}
int main(){
while(cin>>q[0] && q[0]){
for(int i = 1; i < 24 ; i++) cin>>q[i];
int depth = 0;
while(!dfs(0,depth,-1)) depth++;
if(!depth) cout<<"No moves needed"<<endl;
else{
for(int i = 0; i < depth; i++){
printf("%c",path[i]+'A');
}
cout<<endl;
}
cout<<q[6]<<endl;
}
return 0;
}
leetcode
605.岛屿的最大面积
题目描述
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
思路
这题就是DFS的连通块模型,本质上就是寻找最大的连通块的大小
代码描述
class Solution {
int N = 55;
int n,m;
int dx[] = new int[] {0,-1,0,1};
int dy[] = new int[] {-1,0,1,0};
boolean[][] st = new boolean[N][N];
public int maxAreaOfIsland(int[][] grid) {
int ans = 0;
n = grid.length;
m = grid[0].length;
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
if(grid[i][j]!=0){
ans = Math.max(ans,dfs(i,j,grid));
}
}
}
return ans;
}
public int dfs(int x,int y,int[][]grid){
int res = 1;
st[x][y] = true;
for(int i = 0;i < 4;i++){
int a = x + dx[i];
int b = y + dy[i];
if(a < 0 || a>=n || b<0 || b>=m || grid[a][b]==0) continue;
if(st[a][b]) continue;
res += dfs(a,b,grid);
}
return res;
}
}