DFS之剪枝

news/2025/1/10 17:06:57/

常用减枝顺序:

  1. 优化搜索顺序:大部分情况下,我们应该优先搜索分支较少的结点
  2. 排除等效冗余
  3. 可行性剪枝
  4. 最优性剪枝
  5. 最优化搜索(DP)

165. 小猫爬山

翰翰和达达饲养了 N N N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W W W,而 N N N 只小猫的重量分别是 C 1 、 C 2 … … C N C_1、C_2……C_N C1C2……CN

当然,每辆缆车上的小猫的重量之和不能超过 W W W

每租用一辆缆车,翰翰和达达就要付 1 1 1 美元,所以他们想知道,最少需要付多少美元才能把这 N N N 只小猫都运送下山?

输入格式

1 1 1 行:包含两个用空格隔开的整数, N N N W W W

2.. N + 1 2..N+1 2..N+1 行:每行一个整数,其中第 i + 1 i+1 i+1 行的整数表示第 i i i 只小猫的重量 C i C_i Ci

输出格式

输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围

1 ≤ N ≤ 18 1≤N≤18 1N18,
1 ≤ C i ≤ W ≤ 1 0 8 1≤C_i≤W≤10^8 1CiW108

输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

思路:

  • 在搜索的时候,遍历每只猫,把每只猫放在一辆已经租用的缆车上面,或者是重新租一辆缆车
  • 优化搜索顺序:应该首先遍历重量较大的猫,重量大的猫相对于重量较小的猫所产生的分支数量更少
  • 可行性剪枝:如果当一辆缆车加上一只猫的重量超过缆车的总和后,则直接剪枝
  • 最优化剪枝:如果当前的缆车数量大于当前所记录的结果答案,则直接返回
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 20;
int n, m, w[N], sum[N], ans = N;void dfs(int u, int k){// 最优化剪纸if(k > ans) return;if(u == n){ans = k;return;}for (int i = 0; i < k; i++){if(sum[i] + w[u] <= m){ // 可行性剪枝sum[i] += w[u];dfs(u + 1, k);sum[i] -= w[u]; // 恢复现场}}sum[k] = w[u];dfs(u + 1, k + 1);sum[k] = 0; // 恢复现场
}int main(){cin >> n >> m;for (int i = 0; i < n; i++) cin >> w[i];sort(w, w + n);reverse(w, w + n); // 从大到小dfs(0, 0);cout << ans << endl;return 0;
}

166. 数独

数独 是一种传统益智游戏,你需要把一个 9 × 9 9×9 9×9 的数独补充完整,使得数独中每行、每列、每个 3 × 3 3×3 3×3 的九宫格内数字 1 ∼ 9 1∼9 19 均恰好出现一次。

请编写一个程序填写数独。

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 81 81 81 个字符,代表数独的 81 81 81 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字( 1 − 9 1−9 19)或一个 .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 end 的单行,表示输入结束。

输出格式

每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

思路:tpl

  • 暴力搜索,但是直接暴力搜索会超时,因此需要进行剪枝
  • 优化搜索顺序:很明显,我们肯定是从当前能填合法数字最少的位置开始填数字
  • 排除等效冗余:任意一个状态下,我们只需要找一个位置填数即可,而不是找所有的位置和可填的数字
  • 位运算:很明显这里面check判定很多,我们必须优化这个check,所以我们可以对于,每一行,每一列,每一个九宫格,都利用一个九位二进制数保存,当前还有哪些数字可以填写
  • lowbit:我们这道题目当前得需要用 lowbit运算取出当前可以能填的数字
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 9, M = 1 << N;int ones[M], map[M];
int row[N], col[N], cell[3][3];
char str[100];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;
}void draw(int x, int y, int t, bool is_set)
{if (is_set) str[x * N + y] = '1' + t;else str[x * N + y] = '.';int v = 1 << t;if (!is_set) v = -v;row[x] -= v;col[y] -= v;cell[x / 3][y / 3] -= v;
}int lowbit(int x)
{return x & -x;
}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 * N + j] == '.'){int state = get(i, j);if (ones[state] < minv){minv = ones[state];x = i, y = j;}}int state = get(x, y);for (int i = state; i; i -= lowbit(i)){int t = map[lowbit(i)];draw(x, y, t, true);if (dfs(cnt - 1)) return true;draw(x, y, t, false);}return false;
}int main()
{for (int i = 0; i < N; i ++ ) map[1 << i] = i;for (int i = 0; i < 1 << N; i ++ )for (int j = 0; j < N; j ++ )ones[i] += i >> j & 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';draw(i, j, t, true);}else cnt ++ ;dfs(cnt);puts(str);}return 0;
}

167. 木棒

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 50 50 个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

输入格式

输入包含多组数据,每组数据包括两行。

第一行是一个不超过 64 64 64 的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。

输出格式

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围

数据保证每一节木棍的长度均不大于 50 50 50

输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5

思路:

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>using namespace std;const int N=70;int w[N],sum,length,n;
bool st[N];bool dfs(int u,int part,int start)//第u组,part第u组的已有长度,start表示第u组的枚举位置;
{if(u*length==sum) return true;//如果总长度到达了,返回trueif(part==length) return dfs(u+1,0,0);//true要一直持续不断的返回,false同理for(int i=start;i<=n;i++){if(st[i]) continue;if(w[i]+part>length) continue;st[i]=true;//标记已经使用if(dfs(u,w[i]+part,i+1)) return true;//因为前i个棍子都在第u组枚举了,所以直接从第i+1根棍子开始枚举st[i]=false;//返回上层分支时要恢复现场(枚举该组不选择第i根根子的方案)if(!part||w[i]+part==length) return false;//如果第一根失败了或者最后一根失败了,就一定失败int j=i;//如果i失败了,那么长度跟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,length=1;for(int i=1;i<=n;i++){scanf("%d",&w[i]);sum+=w[i];//总和}//倒着排序,以减少分支sort(w+1,w+n+1);reverse(w+1,w+n+1);while(1)//枚举length的长度{if(sum%length==0&&dfs(0,0,0)){printf("%d\n",length);break;}length++;}}
}

168. 生日蛋糕

7 7 7 17 17 17 日是 Mr.W 的生日,ACM-THU 为此要制作一个体积为 N π Nπ Nπ M M M 层生日蛋糕,每层都是一个圆柱体。

设从下往上数第 i i i 层蛋糕是半径为 R i R_i Ri,高度为 H i H_i Hi 的圆柱。

i < M i<M i<M 时,要求 R i > R i + 1 R_i>R_{i+1} Ri>Ri+1 H i > H i + 1 H_i>H_{i+1} Hi>Hi+1

由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q Q Q 最小。

Q = S π Q=Sπ Q=Sπ ,请编程对给出的 N N N M M M,找出蛋糕的制作方案(适当的 R i R_i Ri H i H_i Hi 的值),使 S S S 最小。

Q Q Q 外,以上所有数据皆为正整数。

输入格式

输入包含两行,第一行为整数 N N N,表示待制作的蛋糕的体积为 N π Nπ Nπ

第二行为整数 M M M,表示蛋糕的层数为 M M M

输出格式

输出仅一行,是一个正整数 S S S(若无解则 S = 0 S=0 S=0)。

数据范围

1 ≤ N ≤ 10000 1≤N≤10000 1N10000,
1 ≤ M ≤ 20 1≤M≤20 1M20

输入样例:
100
2
输出样例:
68

思路:

记最底层为 m m m,很容易观察得出,表面积的公式为 S 总 = S m 层上侧面积 S_总=S_{m层上侧面积} S=Sm层上侧面积 + ∑ i = 1 m 2 π R i H i +∑_{i=1}^{m}2πR_iH_i +i=1m2πRiHi

而体积为 V 总 = ∑ i = 1 m π R i 2 H i V_总=∑_{i=1}^{m}πR^2_iH_i V=i=1mπRi2Hi

有了两个公式,还有题目给出的每层最小高度和最小半径,就知道可以用剪枝+暴搜来做这个题

  1. 优化搜索顺序

    • 层间:从下到上

    • 层内:先枚举半径再枚举高(半径相对于高来说对体积的影响较大,因为半径是平方级别),半径由大到小,高度由大到小

  2. 可行性剪枝

    记总体积为 n n n,当前层位 u u u, 第 u u u 层的高度为 H u H_u Hu, 半径为 R u R_u Ru, 体积为 V u V_u Vu,第 m m m 层到第 u u u 层体积的累计值 V V V

    1. 对于 R R R,当前为第 u u u 层,第 u u u 层的体积为 V u V_u Vu R R R 最小的取值应该是当前的层号 u u u R R R 的最大值应该由两部分决定
    • u + 1 u+1 u+1 层的半径减 1 1 1, 记 R u + 1 − 1 R_{u+1}−1 Ru+11
    • u u u 层体积的最大值除第 u u u 层高度的最小值 u u u

    这两者的最小值,故有以下等式成立 u ≤ R u ≤ min ⁡ ( R u + 1 − 1 , n − m i n ∑ i = 1 u − 1 V i − V u ) u≤R_u≤\min({R_{u+1}−1,\sqrt\frac{n−min∑_{i=1}^{u−1}V_i−V}{u}}) uRumin(Ru+11,unmini=1u1ViV )

    1. 对于第 u u u 层高度 h h h 的推导同理,高度 h h h 的取值的最小值应该大于等于层号 u u u,高度的最小值由两部分决定
    • H u + 1 − 1 H_{u+1}−1 Hu+11
    • u u u 层体积的最大值除第 u u u 层的底面积最小值

    故同理可得出下列等式 u ≤ H u ≤ min ⁡ ( H u + 1 − 1 , n − m i n ∑ i = 1 u − 1 V i − V R u 2 ) u≤Hu≤\min({H_{u+1}−1,\frac{n−min∑_{i=1}^{u-1}V_i−V}{R^2_u}}) uHumin(Hu+11,Ru2nmini=1u1ViV)

    1. 考虑体积的剪枝:预处理前 u u u 层的体积最小值 m i n ∑ i = 1 u − 1 V i min∑^{u−1}_{i=1}V_i mini=1u1Vi,会有 V + m i n ∑ i = 1 u − 1 V i ≤ n V+min∑^{u−1}_{i=1}V_i≤n V+mini=1u1Vin

    2. 推表面积公式和体积公式的关系

      第一层到第 u u u 层的表面积有(不考虑 π π π): S 1 − u = 2 ∑ i = 1 u R i H i = 2 R u + 1 ∑ i = 1 u R u + 1 R i H i > 2 R u + 1 ∑ i = 1 u R i 2 H i S_{1−u}=2∑_{i=1}^{u}R_iH_i=\frac{2}{R_{u+1}}∑_{i=1}^{u}R_{u+1}R_iH_i>\frac{2}{R_{u+1}}∑_{i=1}{u}R^2_iH_i S1u=2i=1uRiHi=Ru+12i=1uRu+1RiHi>Ru+12i=1uRi2Hi

      第一层到第 u 层的体积有: n − V = ∑ i = 1 u R i 2 H i n−V=∑_{i=1}^{u}R^2_iH_i nV=i=1uRi2Hi

      所以惊奇地发现: S 1 − u > 2 ( n − V ) R u + 1 S_{1−u}>\frac{2(n−V)}{R_{u+1}} S1u>Ru+12(nV)

      因此 S 总 = S + S 1 − u > = S a n s S_总=S+S_{1−u}>=S_{ans} S=S+S1u>=Sans,即 S + 2 ( n − V ) R u + 1 > = S a n s S+\frac{2(n−V)}{R_{u+1}}>=S_{ans} S+Ru+12(nV)>=Sans 时就可以剪枝掉(最优性剪枝)

  3. 最优性剪枝

    记第 m m m 层到第 u u u 层表面积的累计值 S S S,第 1 1 1 到第 u − 1 u−1 u1 层表面积的最小值为 m i n ∑ i = 1 u − 1 S i min∑^{u−1}_{i=1}S_i mini=1u1Si

    应该有 S + m i n ∑ i = 1 u − 1 S i < r e s S+min∑^{u−1}_{i=1}S_i<res S+mini=1u1Si<res

#include<iostream>
#include<cmath>
using namespace std;const int N = 24, INF = 1e9;int n, m;
int minv[N], mins[N];
int res = INF;//记录每层的半径和高,因为会用到上一层的高度
int R[N], H[N];//u当前层次,v当前处理的体积和,s当前处理的面积和
void dfs(int u, int v, int s)
{if(v + minv[u] > n) return;if(s + mins[u] >= res) return;if (s + 2 * (n - v) / R[u + 1] >= res) return;if(!u){if(v == n) res = s;return;}//搜索顺序,先R后H,从大到小for(int r = min(R[u + 1] - 1,(int)sqrt((n - v - minv[u - 1]) / u)); r >= u; r--)for(int h = min(H[u + 1] - 1, (n - v - minv[u - 1]) / r / r); h >= u; h--){H[u] = h, R[u] = r;//最底层的时候需要加上r*rint t = u == m ? r * r : 0;dfs(u - 1, v + r * r * h, s + 2 * r * h + t);}
}int main()
{cin >> n >> m;for(int i = 1; i <= m; i++){minv[i] = minv[i - 1] + i * i * i;mins[i] = mins[i - 1] + 2 * i * i;}//m+1层不存在,作为哨兵,减少边界情况的判断R[m + 1] = H[m + 1] = INF;dfs(m, 0, 0);if(res == INF) res = 0;cout << res << endl;return 0;
}

详细见博客内容:http://47.108.226.193/index.php/archives/141/


http://www.ppmy.cn/news/1562025.html

相关文章

《零基础Go语言算法实战》 【题目 1-15】字符串的比较

《零基础Go语言算法实战》 【题目 1-15】字符串的比较 请用 Go 语言实现一个算法&#xff0c;在不使用额外存储结构的条件下判断一个字符串的所有字 符是否全都相同&#xff0c;字符串的长度不能超过 3000。 【解答】 ① 思路。 本题需要实现一个算法来判断字符串中的所有…

【System Verilog and UVM基础入门25】功能覆盖率的实现

tdc_coverage.sv 让代码开口说话!!! 记住,代码是最可靠的朋友,它永远不会说谎! `uvm_analysis_imp_decl(_in_monitor_export) `uvm_analysis_imp_decl(_out_monitor_export)class tdc_coverage extends uvm_component;`uvm_component_utils(tdc_coverage)tdc_config m…

ESP32 IDF VScode出现头文件“无法打开 源 文件 ”,并有红色下划线警告

问题背景&#xff1a; ESP32 IDF VScode出现头文件“无法打开 源 文件 ”&#xff0c;并有红色下划线警告&#xff1a; 解决办法&#xff1a; 在工程里面的.vscode文件夹下&#xff0c;检查是否存在c_cpp_properties.json文件&#xff0c;如果没有可以手动创建添加。如图…

升级 Spring Boot 3 配置讲解 — 新版本的秒杀系统怎么做?

学会这款 &#x1f525;全新设计的 Java 脚手架 &#xff0c;从此面试不再怕&#xff01; 1. Spring Boot 3 升级指南 在升级 Spring Boot 3 之前&#xff0c;首先需要确保你的项目已经升级到 Java 17&#xff0c;因为 Spring Boot 3 不再支持 Java 8 和 Java 11。接下来&…

文件读写到SQLite数据库的方法

在 SQLite 数据库中&#xff0c;将文件读写到数据库的常见方法主要有以下几种&#xff1a; 1. 将文件以 BLOB 类型存储 BLOB&#xff08;Binary Large Object&#xff09; 是 SQLite 中的二进制数据类型&#xff0c;可以直接用来存储文件内容。 步骤&#xff1a; 创建表 创建一…

HTML+CSS+JS制作中国传统节日主题网站(内附源码,含5个页面)

一、作品介绍 HTMLCSSJS制作一个中国传统节日主题网站&#xff0c;包含首页、节日介绍页、民俗文化页、节日活动页、联系我们页等5个静态页面。其中每个页面都包含一个导航栏、一个主要区域和一个底部区域。 二、页面结构 1. 顶部横幅区 包含传统中国风格的网站标题中国传统…

解决Vscode中使用netdb.h的getaddrinfo和addrinfo会报错的方法

原文地址&#xff1a;https://kashima19960.github.io/2024/12/03/解决Vscode中使用netdb.h的getaddrinfo和addrinfo会报错的方法/&#xff0c;一般有最新的修改都是在我的个人博客里面&#xff0c;所以在当前平台的更新会比较慢&#xff0c;请见谅&#x1f603; 前言 博主最近…

JAVA 使用apache poi实现EXCEL文件的输出;apache poi实现标题行的第一个字符为红色;EXCEL设置某几个字符为别的颜色

设置输出文件的列宽&#xff0c;防止文件过于丑陋 Sheet sheet workbook.createSheet(FileConstants.ERROR_FILE_SHEET_NAME); sheet.setColumnWidth(0, 40 * 256); sheet.setColumnWidth(1, 20 * 256); sheet.setColumnWidth(2, 20 * 256); sheet.setColumnWidth(3, 20 * 25…