搜索优化技巧:剪枝

  • 定义

       剪枝, 就是减少搜索规模, 尽早排除不可能的选项,和一定不会成为最优解的选项,形象的说就好像剪掉了搜索树的枝,所以叫做剪枝。

  • 剪枝的常用方法
  1. 优化搜索顺序

       在一些搜索问题中,搜索树的各个层次, 各个分支之间的顺序不是固定的,不同的搜索顺序会产生不同的搜索树形态。
       通俗的说就是 将所给的数据进行从小到大或者从大到小的排序,然后再进行搜索。

  1. 排除等效冗余

       在根索过程中,如果我们能够判定从搜索树的当前节点上沿着某几条不同分支到达的子树是等效的,那么只需要对其中的一条分支执行搜索。
       就是当一个数据不能得到解或者最优解时, 后面和它一样大的数据就不进行搜索。

  1. 可行性剪枝

       在搜索过程中,及时对当前状态进行检查,如果发现分支已经无法到达递归边界,就执行回溯。这就好比我们在道路上行走时,远远看到前方是一个死胡同,就应该立即折返绕路,而不是走到路的尽头再返回。某些题目条件的范围限制是一个区间, 此时可行性剪枝也被称为“上下界剪枝”。

  1. 最优性剪枝

       在最优化问题的搜索过程中,如果当前花费的代价已经超过了当前搜到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案。此时可以停止对当前分支的搜索,执行回溯。

  1. 记忆化

       可以记录每个状态的搜索结果,在重复遍历一个状态时直接检索并返回。我们对图进行深度优先遍历时,标记- 个节点是否已经被访问过。

题目大意

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

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

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

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

输入格式

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

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

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

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

输出格式

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

样例

输入
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出
6
5

思路

       我们可以从小到大枚举原始木棒的长度len (也就是枚举答案)。当然, len 应该的位置。 是所有木棍长度总和sum的约数,并且原始木棒的根数cnt 就等于sum / len.
       对于枚举的每个len,我们可以依次搜索每根原始木棒由哪些木棍拼成。我们搜索的方向为: 已经拼好的原始木棒根数,正在拼的原始木棒的当前长度,每的"stcks"个木棍的使用情况. 在每个状态下,我们从尚未使用的木棍中选择一个,尝试拼到当前的原始木棒里,然后递归到新的状态。递归边界就是成功拼好cnt 根原始木棒.
由于这样的搜索效率较低,所以需要进行剪枝。

  1. 优化搜索顺序

把木棍长度从大到小排序,优先尝试较长的木棍。

  1. 排除等效冗余

        (1)可以限制先后加入一根原始木棒的木棍长度是递减( ≥ )的。这是因为先拼上一 根长度为 x 的木棍,再拼上一根长为 y 的木棍(x<y),与先拼上y再拼上 x 显然是等效的,只需要搜索其中一种。

        (2)对于当前原始木棒,记录最近一次尝试拼接的木棍长度。如果分支搜索失败回溯,不再尝试向该木棒中拼接其他相同长度的木棍(必定也会失败)。

        (3) 如果在当前原始木棒中“尝试拼入的第一根木棍”的递归分支就返回失败,那么直接判定当前分支失败,立即回溯。这是因为在拼入这根木棍前,面对的原始木棒都是“空”的(还没有进行拼接),这些木棒是等效的。木棍拼在当前的木棒中失败,拼这就好比 在其他木棒中一样会失败。

       (4)如果在当前原始木棒中拼入一根木棍后,木棒恰好被拼接完整,并且“接下来拼接剩余的原始木棍" 的递归分支失败,那么判定当前分支失败,立即回溯。

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[100];
bool  vis[100];
int len, n, cnt;
//正在拼第stick根原始木棍 已经拼成了stick - 1根原始木棍
//第stick根木棍的当前长度为cab
//拼接到stick根木棍中的上一根木棍为last 
bool dfs(int stick, int cab, int last){
	//所有的木根都已经拼好了 就return true
	if(stick > cnt) return true;
	//第stick根木棍已经拼好了 就拼下一根木棍

	if(cab == len) return dfs(stick + 1, 0, 1);
	int fail = 0;  //剪枝  ====   2   避免搜索一样长的 
	for(int i = last; i <= n; i++){
		if(!vis[i] && cab + a[i] <= len && fail != a[i]){
			vis[i] = true;	
			if(dfs(stick, cab + a[i], i + 1)) return true;
			fail = a[i];
			vis[i] = false;
	    	if(cab == 0 || cab + a[i] == len ) return false;  //  剪枝 ==== 3 === 4 
		}
	} 
	return false;  //所有分支都尝试完了 搜索失败 
}

int main(){
	
	while(cin >> n && n){
		int sum = 0, val = 0;
		//sum存一共的长度   val存最长的一根木棍长度是多少 
		for(int i = 1; i <= n; i++){
			scanf("%d", a + i);
			sum += a[i];
			val = max(val, a[i]);
		}
		sort(a + 1, a + 1 + n);
		reverse(a + 1, a + 1 + n);
		for( len = val; len <= sum; len++){
			if(sum % len) continue;    //剪枝 =====  1
			cnt = sum / len;  //想要拼成的木棍的长度为len  一共有cnt根
			memset(vis, false, sizeof vis); 
			 if(dfs(1, 0, 1)) break;  //正在拼第1根原始木棍 这根木棍拼成了的长度为0 上一根木棍为1 
			 
		}
		cout << len << endl;
	}
	return 0;
} 
发布了54 篇原创文章 · 获赞 155 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_45432665/article/details/104107750