本系列的开篇之作,先介绍一下剪枝的概念
一、什么是剪枝
- 搜索的进程可以看成是从树根出发,遍历一颗倒置的树——搜索树的过程。剪枝就是通过某种判断,避免一些不必要的遍历过程
二、剪枝的原则
- 正确性
- 准确性
- 高效性
三、 深度优先搜索的优化技巧
- 优化搜索顺序
- 排除等效冗余
- 可行性剪枝
- 最优性剪枝
- 记忆化
四、树的划分(可行性剪枝、上下界剪枝)
问题描述——将整数 n 划分成 k 份,且每份不能为空,问有多少种不同的分法?当 n = 7,k = 3 时,下面三种分法被认为是相同的,1,1,5;1,5,1;5,1,1。
输入格式——输入文件只有一行,为两个整数 n 和 k(\(6 < n \leq 200,2 \leq k \leq 6\))
输出格式——输出文件只有一行,为一个整数,即不同的分法数
样例输入
7 3
样例输出
4
样例解释—— 4 种分法为 1,1,5;1,2,4;1,3,3;2,2,3
思路分析——将数字 n 划分成 k 份,就是求 \(x_1 + x_2+...+x_k = n\) 方程的解,依次枚举 \(x_1到x_k\) 的值,然后判断
剪枝分析
- 由于分解数不考虑顺序,因此设定分解数依次递增,所以扩展结点时的“下届”应是不小于前一个扩展结点的值,即 \(a[i-1] \leq a[i]\)
- 假设我们将 n 已经分解成了 \(a[1] + a[2]+ ...+a[i-1]\),则 \(a[i]\) 的最大值为将 i~k 这 k-i+1 份平均划分,即设 \(m = n-(a[1]+a[2]+...+a[i-1])\),则 \(a[i]\leq m/(k-i+1)\),所以扩展结点的“上届”是 \(\frac{m}{k-i+1}\)
代码:
#include <iostream> using namespace std; int n,m,a[8],s= 0; void Solve(int k); int main() { cin>>n>>m; a[0] = 1; Solve(1); cout<<s<<endl; return 0; } void Solve(int k) { if(n == 0) return; if(k == m) { if(n >= a[k-1]) s++; return; } for(int i = a[k-1];i<=n/(m-k+1);i++) { a[k] = i; n -= i; Solve(k+1); n += i; } }