算法学习系列(四十五):DFS之剪枝与优化

目录

  • 引言
  • DFS之剪枝与优化
  • 一、小猫爬山
  • 二、木棒
  • 三、数独
  • 四、总结

引言

关于这个 D F S DFS DFS 的剪枝和优化确实难度是非常的大,从我这篇文章的思路和代码量上就能看出来不是一般的难度,而且难度不亚于 D P DP DP ,而且这个 D F S DFS DFS 也是花费了我三天的时间才基本把这几道例题给搞懂了,并且这种题就是没有固定的模型和套路,每个题都不一样,只有你多做题,这样在考场上才能想到这道题好像跟之前做过的题有点相似,然后再去套用模型。不可能短时间去创造一种算法,所以还是继续刷题吧,加油!


DFS之剪枝与优化

  • 优化搜索顺序:大部分情况下,我们应该优先搜索分支较少的节点。比如背包问题应该先放体积大的物品
  • 排除等效冗余:组合问题中, 1 , 2 , 3 1,2,3 123 1 , 3 , 2 1,3,2 132 是一样的,可以用一个 s t a r t start start 来规定初始
  • 可行性剪枝:发现当前状态不合法,退出
  • 最优性剪枝:如果发现当前方案不可能是最优解了,就退出

搜索顺序模型:
1.给一堆物品及其单个体积和单个篮子的容量,问最小的篮子数。解决方案:枚举每个物品,该物品要么加入到已有的篮子里,要么新开一个篮子。
2.给一堆物品及其单个体积,问刚好能把所有篮子都装满的最小篮子容量。解决方案:枚举每个篮子,然后在每个篮子中枚举每个物品。
3.给一堆物品,装进篮子里,但都有一定的限制。解决方案:枚举每个物品


一、小猫爬山

标签:搜索、深度优先搜索、DFS

思路:首先要保证正确性,也就是枚举的所有方案必须是不重不漏的,然后才对其进行优化,不能本末颠倒了。我们可以按小猫的编号进行枚举,当前小猫要么新坐一辆缆车,要么从已有的缆车里挑一个坐。首先是优化搜索顺序,可以把小猫的重量从大到小排序,进行枚举,然后就是如果当前的方案已经不是最优解了,直接退出,再有就是如果当前缆车已经容不下当前小猫了,退出。

题目描述:

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。翰翰和达达只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。数据范围
1≤N≤18,1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 20;int n, m;
int w[N];
int sum[N];
int ans = N;void dfs(int u, int k)  // 正在选编号为u的小猫,当前组数为k k为实际已有的组数 
{if(k >= ans) return;  // 若u已经为n,并且k==ans的话,那么也非最优解 if(u == n)  // 说明前n个小猫已经加到组里了{ans = k;return;}for(int i = 1; i <= k; ++i)  // k为第k组 {if(w[u] + sum[i] <= m){sum[i] += w[u];dfs(u+1,k);  // 这里不返回bool的原因是要找最值,而非判断是否可行 sum[i] -= w[u];	} }sum[k+1] = w[u];dfs(u+1, k+1);sum[k+1] = 0;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);cin >> n >> m;for(int i = 0; i < n; ++i) cin >> w[i];sort(w, w + n, greater<int>());  // 优化搜索顺序dfs(0,0);cout << ans << endl;return 0;
}

二、木棒

标签:搜索、DFS、剪枝

思路:首先这道题是找最小的木棒长度,也就是组数是无所谓的,只要能凑出来每组最小的容量即可。之前求最小组数的思路是,枚举每一个物品,然后这件物品,要么加入到当前任意一个组里,要么新开一个组,在此基础上进行剪枝优化。而这道题是从小到大枚举容量,如果当前容量恰好能装满每一个组,那就是答案了。然后在每一个组的基础上枚举每一件物品,放入该组里,如果当前组满了,然后再开下一个组,然后在此进行剪枝优化。首先是优化搜索顺序,先从大到小排序,然后最最小的也就是最长的那根木棍了。然后因为是每个组都要枚举每一件物品,所以避免出现组合型冗余,规定一个 s t a r t start start 枚举顺序,因为之前的已经枚举过了,如果继续从 0 0 0 开始,那么还要枚举重复的,比如说: 1 , 2 , 3 和 3 , 1 , 2 1,2,3和3,1,2 1,2,33,1,2 之类的方案。然后就是可行性剪枝了,就是如果当前组装不下,那就不装了,另外如果当前木棍是第一个装的,但没有满足要求,那么该类方案一定没有解,因为如果该木棍是下一组的,可以通过在组内排顺序把它变成第一个,那么上一次没有满足要求,这次也就不会满足要求了。其次如果该木棍是最后一根刚好装满了,但是之后的方案却没有满足,那么该方案之后的所有都不会满足的,因为如果满足要求了,那么肯定会有几根木棍凑出来最后一根,因为这两长度是一样的,所以可以替换,但因为之前的最后一根已经不满足了,所以肯定不会有满足要求的情况存在。

题目描述:

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。输入格式
输入包含多组数据,每组数据包括两行。第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。数据范围
数据保证每一节木棍的长度均不大于 50。输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 100;int n;
int w[N];
bool st[N];
int sum, len;bool dfs(int u, int cur, int start)  // 当前u组里,目前的容量为cur,从start开始
{if(u * len == sum) return true;  // 说明前u个组里已经满了,并且长度已经达到总和if(cur == len) return dfs(u+1,0,0);  // 当前组已经满了,新开下一个组for(int i = start; i < n; ++i)  // 排序冗余{if(st[i]) continue;  // 可行性剪枝 只能用一次if(cur + w[i] <= len)  // 可行性剪枝 不能超过容量{st[i] = true;if(dfs(u,cur+w[i],i+1)) return true;st[i] = false;}if(!cur || cur + w[i] == len) return false;  // 可行性剪枝int j = i + 1;while(j < n && w[i] == w[j]) j++;i = j - 1;}return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);while(cin >> n, n){sum = 0, len = 0; for(int i = 0; i < n; ++i) {cin >> w[i];sum += w[i];len = max(len,w[i]);}sort(w, w + n, greater<int>());memset(st, 0, sizeof st);while(1){if(sum % len == 0 && dfs(0,0,0)){cout << len << endl;break;}len++;}} return 0;
}

三、数独

标签:搜索、深度优先搜索、DFS

思路:这道题就是枚举每一个空格,如果最后没有空格了,说明正确直接返回。每一行每一列和每一个田字格可以用一个 9 9 9 位的二进制数来表示,每一位为 1 1 1 说明当前位是空位。然后优化顺序,可以每次找到最少可能的格子来先枚举,可以用与运算行列和当前的田字格,来知道当前格子的 9 9 9 位二进制状态,然后可以提前打表,知道这个数是有几个 1 1 1 组成的,然后枚举该格子的多种可能,然后可以用 l o w b i t lowbit lowbit 来知道最后一位 1 1 1 对应的数,然后在可以通过提前打表知道该数所对应的位,然后暴搜各种可能即可。

题目描述:

数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得数独中每行、每列、每个 3×3 的九宫格内数字 
1∼9 均恰好出现一次。请编写一个程序填写数独。输入格式
输入包含多组测试用例。每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。您可以假设输入中的每个谜题都只有一个解决方案。文件结尾处为包含单词 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

示例代码:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 9, M = 1 << 9;int ones[M], mmap[M];
int col[N], row[N], cell[3][3];
char str[100];void init()
{for(int i = 0; i < N; ++i) col[i] = row[i] = (1 << 9) - 1;for(int i = 0; i < 3; ++i){for(int j = 0; j < 3; ++j){cell[i][j] = (1 << 9) - 1;}}
}void draw(int x, int y, int t, bool is_set)
{if(is_set) str[x * N + y] = t + '1';  // 这里写1,是因为之后 -v 的缘故 else str[x * N + y] = '.';int v = 1 << t;if(!is_set) v = -v;row[x] -= v;  // v得是从1开始的 已经设置了说明当前该位的1要变为0 col[y] -= v;cell[x/3][y/3] -= v;
}int get(int x, int y)
{return row[x] & col[y] & cell[x/3][y/3];
}int lowbit(int x)
{return x & -x;
}bool dfs(int cnt)
{if(!cnt) return true;int minv = 10, 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))  // 枚举1的次数 {int t = mmap[lowbit(i)];  draw(x,y,t,true);if(dfs(cnt-1)) return true;draw(x,y,t,false); }return false;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);for(int i = 0; i < N; ++i) mmap[1 << i] = i;for(int i = 0; i < M; ++i){for(int j = 0; j < N; ++j){if(i >> j & 1) ones[i]++;}}while(cin >> str, str[0] != 'e'){init();int cnt = 0;for(int i = 0; i < N; ++i){for(int j = 0; j < N; ++j){if(str[i * N + j] != '.'){int t = str[i * N + j] - '1';draw(i,j,t,true);}else cnt++;}}dfs(cnt);cout << str << endl; }return 0;
}

四、总结

  • 关于小猫爬山和木棒这两个题,前者是枚举每个猫在其中遍历每个组,后者是枚举每个组在其中遍历每个木棍,前者是要求最小的组数,后者要求最短的木棒,前者求最值,后者求是否可行,前者是因为每个组可能装不满,后者是要每个组装满了才能装下一个组,所以怎么说呢,感觉自己还没有那么举一反三的能力,只能按模型去写对应的写法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/802865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言支持重载吗?如何实现重写?

Go语言不支持传统意义上的函数和方法重载。在Go语言中&#xff0c;函数名或方法名不能相同但参数列表不同&#xff0c;因为这会导致编译错误。 然而&#xff0c;可以通过方法重写&#xff08;override&#xff09;来实现类似的功能。方法重写是指在子类中定义一个与父类同名的…

C语言进阶课程学习记录-第27课 - 数组的本质分析

C语言进阶课程学习记录-第27课 - 数组的本质分析 数组实验-数组元素个数的指定实验-数组地址与数组首元素地址实验-指针与数组地址的区别小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 数组 实验-数…

Hot100【十一】:编辑距离

// 定义dp[i][j]: 表示word1前i个字符转换到word2前j个字符最小操作数 // 初始化dp[m1][n1] class Solution {public int minDistance(String word1, String word2) {int m word1.length();int n word2.length();// 1. dp数组int[][] dp new int[m 1][n 1];// 2. dp数组初…

分布式系统接口限流方案

方案一、 Guava工具包 实现单机版限流 Demo的Git地址&#xff1a;https://gitee.com/deepjava/test-api-limit.git 使用Google的Guava工具包提工单 RateLimiter类 可以实现单机状态下的接口限流 RestController RequestMapping("/test") public class ApiLimitCon…

IO流:将文件从A复制到B,并实现复制过程进度条的实现

private static boolean copyFile(String strFileA, String strFileB) {// 使用try资源块 ,其中创建的流对象可以自动关闭try (FileInputStream inputStream new FileInputStream(strFileA); // 输入流FileOutputStream outputStream new FileOutputStream(strFileB) // 输…

【Linux】进程的状态(运行、阻塞、挂起)详解,揭开孤儿进程和僵尸进程的面纱,一篇文章万字讲透!!!!进程的学习②

目录 1.进程排队 时间片 时间片的分配 结构体内存对齐 偏移量补充 对齐规则 为什么会有对齐 2.操作系统学科层面对进程状态的理解 2.1进程的状态理解 ①我们说所谓的状态就是一个整型变量&#xff0c;是task_struct中的一个整型变量 ②.状态决定了接下来的动作 2.2运行状态 2.…

自动化运维(十)Ansible 之进程管理模块

Ansible的进程管理模块提供了一种强大而灵活的方式来管理和操作各种进程管理器和服务。无论你使用的是Supervisor、Systemd、传统的init脚本还是Runit,这些模块都可以帮助你轻松地管理服务的生命周期。通过合理地使用这些模块,你可以实现服务的自动化管理,提高系统的可靠性和稳…

【闲聊】-网页划词翻译插件

英文之痛 作为程序猿&#xff0c;常常需要接触外文网站&#xff0c;以前很痛苦&#xff0c;现在大模型时代有很多智能工具可以直接翻译&#xff0c;翻译的虽然越来越好&#xff0c;但是还是不如直接看英文能理解本义&#xff0c;相信我&#xff0c;看翻译的理解和看原文的理解…

龙迅LT2611UXC 2 PORT LVDS桥接到HDMI 2.0,内置MCU,颗自行操作

龙迅LT2611UXC描述&#xff1a; LT2611UXC是一个高性能的LVDS到HDMI2.0的转换器&#xff0c;用于STB&#xff0c;DVD应用程序。LVDS输入可以配置为单端口或双端口&#xff0c;有1个高速时钟通道&#xff0c;3~4个高速数据通道&#xff0c;最大运行1.2Gbps/通道&#xff0c;可支…

gpu模拟器总体流程

1、开显存空间&#xff0c;初始化 这里显存就是运行模拟器的机器 2、创建页表&#xff0c;开设备端空间并复制数据 虚拟地址 3、划分形状&#xff0c;传入内核函数&#xff0c;形状参数和设备端数据地址、执行计算 4、复制数据回主机端&#xff0c;释放gpu资源

ORACAL执行计划

概述 | Id | Operation | Name | Rows | Bytes | TempSpc | Cost (%CPU) | Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1…

adb remount

区别 权限 adb remount后push的文件可以获得更高的系统权限&#xff0c;remount操作会将文件系统挂载为可读写模式&#xff0c;可以将文件推送到系统分区&#xff0c;修改系统文件等。直接push的文件只能放在用户可访问的位置&#xff0c;无法修改系统文件。 文件位置 通过…

你对加班是怎么看的

做为一名互联网从业者,不管是开发人员或者是测试人员,都逃不开加班这个话题。虽然简历中并不建议过多表现自己多么能加班,但写简历之前就要对加班有自己的看法,把这些意识融入到简历中去。 1 产生加班的原因 第一种是工作经验较少,业务不熟练,导致正常工作时间无法完成任…

如何在Python中实现设计模式?

如何在Python中实现设计模式&#xff1f; 设计模式是在软件开发中解决常见问题的最佳实践。它们提供了在特定上下文中对软件设计的重复使用性解决方案。Python&#xff0c;作为一种灵活且强大的编程语言&#xff0c;非常适合实现各种设计模式。下面&#xff0c;我将介…

多线程 线程池 Task

多线程概念&#xff1f;优点及缺点&#xff1f; 多线程是指程序中包含多个执行流&#xff08;线程&#xff09;&#xff0c;即在一个程序中可以同时运行多个不同的线程来执行不同的任务&#xff0c;也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。 概括&#x…

手写简易操作系统(二十五)--文件系统第三部分

前情提要 一、文件写入 1.1、file的写入 文件写入比较复杂&#xff0c;函数行数相当多 /*** description: 把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1 * param {file*} file 文件* param {void*} buf 缓存* param {uint32_t} count 写入的字节数…

leetcode2009--使数组连续的最少操作数

1. 题意 给定一个数组&#xff0c;求最少的操作数使得数组连续。 每次操作你可选择一个数&#xff0c;将它变为任意其他数。 leetcode2009 2. 题解 思路&#xff1a;反向考虑&#xff0c;最多能保留多少个数字使得当前数组连续。 就变成了 [ x , x s z − 1 ] [x,xsz-1]…

基于Java+SpringBoot+Vue民宿预约管理系统(源码+文档+部署+讲解)

一.系统概述 随着社会的不断进步与发展&#xff0c;人们经济水平也不断的提高&#xff0c;于是对各行各业需求也越来越高。利用计算机网络来处理各行业事务这一概念更深入人心&#xff0c;由于工作繁忙以及其他的原因&#xff0c;到实体店进行预约也是比较难实施的。如果开发一…

es6新增加的语法

let和const关键字&#xff1a;let和const允许你声明具有块级作用域的变量和常量。这有助于避免使用var时可能出现的变量提升和全局污染问题。模板字符串&#xff1a;使用反引号()可以创建多行字符串和嵌入表达式。 javascript let name world; let greeting Hello, ${name}!…

迪拜公司怎么注册 迪拜公司注册优势 迪拜公司注册条件

一、迪拜公司注册优势 1、税收优势&#xff1a;迪拜是一个没有个人所得税、企业所得税和增 值税的地区&#xff0c;这为注册公司在迪拜提供了巨大的税收优势。 2、地理位置优势&#xff1a;迪拜位于东西方和南北方的交汇点&#xff0c;拥有得天独厚的地理位置。这使得迪拜成为…