DFS(四):剪枝策略
顾名思义,剪枝就是通过一些判断,剪掉搜索树上不必要的子树。在采用DFS算法搜索时,有时候我们会发现某个结点对应的子树的状态都不是我们要的结果,这时候我们没必要对这个分支进行搜索,砍掉这个子树,就是剪枝。
在DFS搜索算法中,剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。应用剪枝策略的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
剪枝策略按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝。
1.可行性剪枝
可行性剪枝就是把能够想到的不可能出现的情况给它剪掉 。该方法判断继续搜索能否得出答案,如果不能直接回溯。
【例1】Sum It Up (POJ 1564)
Description
Given a specified total t and a list of n integers, find all distinct sums using numbers from the list that add up to t. For example, if t = 4, n = 6, and the list is [4, 3, 2, 2, 1, 1], then there are four different sums that equal 4: 4, 3+1, 2+2, and 2+1+1. (A number can be used within a sum as many times as it appears in the list, and a single number counts as a sum.) Your job is to solve this problem in general.
Input
The input will contain one or more test cases, one per line. Each test case contains t, the total, followed by n, the number of integers in the list, followed by n integers x 1 , . . . , x n . If n = 0 it signals the end of the input; otherwise, t will be a positive integer less than 1000, n will be an integer between 1 and 12 (inclusive), and x 1 , . . . , x n will be positive integers less than 100. All numbers will be separated by exactly one space. The numbers in each list appear in nonincreasing order, and there may be repetitions.
Output
For each test case, first output a line containing `Sums of', the total, and a colon. Then output each sum, one per line; if there are no sums, output the line `NONE'. The numbers within each sum must appear in nonincreasing order. A number may be repeated in the sum as many times as it was repeated in the original list. The sums themselves must be sorted in decreasing order based on the numbers appearing in the sum. In other words, the sums must be sorted by their first number; sums with the same first number must be sorted by their second number; sums with the same first two numbers must be sorted by their third number; and so on. Within each test case, all sums must be distinct; the same sum cannot appear twice.
Sample Input
4 6 4 3 2 2 1 1
5 3 2 1 1
400 12 50 50 50 50 50 50 25 25 25 25 25 25
0 0
Sample Output
Sums of 4:
4
3+1
2+2
2+1+1
Sums of 5:
NONE
Sums of 400:
50+50+50+50+50+50+25+25+25+25
50+50+50+50+50+25+25+25+25+25+25
(1)编程思路。
由于题中给出待选数的顺序就是从大到小的,因此我们从第一个数开始,依次往后搜索,将可能的数据都记录下来,每遇到一种满足题意的组合就输出,一直搜索下去,得到所有答案。若没有答案,输出NONE。
定义全局数组int a[12]来保存给出的待选数列表,为输出和式,定义int b[12]保存选中的数据。
递归函数void dfs(int i,int j,int sum)表示从数组a的第i个元素开始选择数据加入到和值sum中,选中的数据a[i]保存到b[j]中。
因为题目中所有待选数都是正数,因此一旦发现当前的和值sum都已经大于t了,那么之后不管怎么选,和值都不可能回到t,所有当sum > t时,可以直接剪掉。
if (sum > t) return;
由于给出的N个数中可以有重复的数,求N个数中取若干个数,这若干个数的和为T的所有情况,但这些情况不能重复。因此,程序中还需要去重。
如果不去重,则对于第1组测试数据4 6 4 3 2 2 1 1,会输出
Sums of 4:
4
3+1 // 式子中的1是倒数第2个1
3+1 // 式子中的1是最后1个1
2+2
2+1+1 // 式子中的2是第3个2
2+1+1 // 式子中的2是第4个2
由于待选数从大到小排列,相同的数据连续放在一起,因此用循环
while(k<n && a[k]==a[k+1]) k++;
可以简单地去重。即某个数作为和式中的加数第1次被选中时,其后连续相同的数不能作为和式中的第1次被选中的加数。
这个去重操作也会剪掉一些枝叶,也可以看成是一个剪枝。
(2)源程序。
#include <stdio.h>
#define N 12
int a[N],b[N];
int t,n,ok;
void dfs(int i,int j,int sum)
{
int k;
if(sum>t) return; // 剪枝1
if(sum==t)
{
printf ("%d",b[0]);
for (k=1;k<j;k++)
printf("+%d",b[k]);
printf("\n");
ok=1;
return;
}
for(k=i;k<n;k++)
{
b[j]=a[k];
dfs(k+1,j+1,sum+a[k]);
while(k<n && a[k]==a[k+1]) // 去重
k++;
}
}
int main()
{
int sum;
while (1)
{
scanf("%d%d",&t,&n);
if (t==0 && n==0) break;
sum=0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
sum=sum+a[i];
}
printf("Sums of %d:\n",t);
ok=0;
if (sum<t)
{
printf("NONE\n");
continue;
}
else
dfs(0,0,0);
if(!ok)
printf("NONE\n");
}
return 0;
}
【例2】Sticks (POJ 1011)。
Description
George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.
Input
The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.
Output
The output should contains the smallest possible length of original sticks, one per line.
Sample Input
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
Sample Output
6
5
(1)编程思路。
定义数组int* stick=new int[n];来保存所输入的n根木棒的长度,数组bool* visit=new bool[n]; 来记录对应的n根木棒是否被用过,初始时值全为false。
由于木棒越长,拼接灵活度越低,因此搜索前先对所有的棒子按降序排序,即将stick数组的元素按从大到小排序。
编写一个递归的搜索函数bool dfs(int* stick,bool* visit,int len,int InitLen,int s,int num),其中参数 len表示当前正在组合的棒长、InitLen表示所求的目标棒长、s(stick[s])表示搜索的起点、num代表已用的棒子数量。如果按InitLen长度拼接,可以将n根木棒全用掉,则函数返回true,搜索成功;否则函数返回false,表示InitLen不可能是所求的最短原始棒长。
令InitLen为所求的最短原始棒长,maxlen为给定的棒子堆中最长的棒子(maxlen=stick[0]);,sumlen为这堆棒子的长度之和,那么所要搜索的原始棒长InitLen必定在范围[maxlen,sumlen]中。
实际上,如果能在[maxlen,sumlen-InitLen]范围内找到最短的InitLen,该InitLen必也是[maxlen,sumlen]范围内的最短;若不能在[maxlen,sumlen-InitLen]范围内找到最短的InitLen,则必有InitLen=sumlen。因此,可以只在[maxlen,sumlen-InitLen]范围内搜索原始棒长。即搜索的循环为:for(int InitLen=maxlen;InitLen<=sumlen-InitLen;InitLen++)
在搜索时,为提高效率,还可以进行剪枝。具体是:
1)由于所有原始棒子等长,那么必有sumlen%Initlen==0,因此,若sumlen%Initlen!=0,则无需对Initlen值进行搜索判断。
2)由于所有棒子已降序排列,在DFS搜索时,若某根棒子不合适,则跳过其后面所有与它等长的棒子。
3)对于某个目标InitLen,在每次构建新的长度为InitLen的原始棒时,检查新棒的第一根棒子stick[i],若在搜索完所有stick[]后都无法组合,则说明stick[i]无法在当前组合方式下组合,不用往下搜索(往下搜索只会令stick[i]被舍弃),直接返回上一层。
(2)源程序
#include<iostream>
#include<algorithm>
using namespace std;
int n; // 木棒数量
int cmp(const void* a,const void* b)
{
return *(int*)b-*(int*)a;
}
bool dfs(int* stick,bool* visit,int len,int InitLen,int s,int num);
int main()
{
while(cin>>n && n)
{
int* stick=new int[n];
bool* visit=new bool[n];
int sumlen=0,i;
bool flag;
for(i=0;i<n;i++)
{
cin>>stick[i];
sumlen+=stick[i];
visit[i]=false;
}
qsort(stick,n,sizeof(stick),cmp);
int maxlen=stick[0];
flag=false;
for(int InitLen=maxlen;InitLen<=sumlen-InitLen;InitLen++)
{
if(!(sumlen%InitLen) ) // 剪枝(1)
if (dfs(stick,visit,0,InitLen,0,0))
{
cout<<InitLen<<endl;
flag=true;
break;
}
}
if(!flag)
cout<<sumlen<<endl;
delete stick;
delete visit;
}
return 0;
}
bool dfs(int* stick,bool* visit,int len,int InitLen,int s,int num)
{
if(num==n)
return true;
int sample=-1;
for(int i=s;i<n;i++)
{
if(visit[i] || stick[i]==sample)
continue; // 剪枝(2)
visit[i]=true;
if(len+stick[i]<InitLen)
{
if(dfs(stick,visit,len+stick[i],InitLen,i,num+1))
return true;
else
sample=stick[i];
}
else if(len+stick[i]==InitLen)
{
if(dfs(stick,visit,0,InitLen,0,num+1))
return true;
else
sample=stick[i];
}
visit[i]=false;
if(len==0) // 剪枝(3)
break;
}
return false;
}
2.最优性剪枝
最优性剪枝,又称为上下界剪枝,是一种重要的剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。
对于求最优解的一类问题,通常可以用最优性剪枝。比如在求迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算。
John and Brus are inside the vault at the moment. They would like to steal everything, but unfortunately they are able to carry diamonds with the total weight not exceeding M.
Your task is to help John and Brus to choose diamonds with the total weight less than or
equal to M and the maximal possible total cost.
Input
Output
Sample Input
2
2 4
3 2
5 3
3 100
4 7 1
5 9 2
Sample Output
6
29
Hint
1 ≤ T ≤ 74,
1 ≤ N ≤ 15,
1 ≤ M ≤ 1000000000 (109),
1 ≤ Wk, Ck ≤ 1000000000 (109).
(1)编程思路。
利用贪心的思想先对箱子进行排序,关键字为性价比(Ck/Wk)。也就是单位重量的价值最高的排第一。搜索的时候采用的剪枝策略有:
剪枝1:之后所有的钻石价值+目前已经得到的价值<=ans,则剪枝。
剪枝2:剩下的重量全部装目前最高性价比的钻石+目前已经得到的价值<=ans,则剪枝。
(2)源程序。
#include <iostream>
#include<algorithm>
using namespace std;
typedef __int64 ll;
int m,n;
ll ans;
bool flag;
struct node
{
ll w,c;
int num;
}lcm[20];
ll bsum[20];
int cmp(struct node a,struct node b)
{
return a.c * b.w > b.c * a.w; // 按性价比降序排列
}
void dfs(int cur,ll sum,int remain)
// cur搜到的当前位置,sum当前的总价值,remain当前还剩多少重量
{
if(remain < 0) return;
if(flag) return;
if(cur == n)
{
if(ans < sum)
ans = sum;
return;
}
if(sum + bsum[cur] <= ans) return; // 剪枝1
if(sum + remain*(lcm[cur].c*1.0/lcm[cur].w) <= ans) // 剪枝2
return;
if(remain == 0) // 因为先贪心了一下,所以第一次恰好凑成m的重量的一定是最优解
{
ans = sum;
flag = true;
return;
}
for(int i = lcm[cur].num;i >= 0;i --)
{
if (remain >= i * lcm[cur].w)
dfs(cur + 1,sum + i * lcm[cur].c,remain - i * lcm[cur].w);
else
ans = sum > ans?sum:ans;
}
}
int main()
{
int t,i;
scanf("%d",&t);
while(t--)
{
ll sumw,sumc;
sumw = sumc = 0;
scanf("%d%d",&n,&m);
for(i = 0;i < n;i ++)
{
scanf("%I64d",&lcm[i].w);
lcm[i].num = i + 1;
sumw += lcm[i].w * (i + 1);
}
for(i = 0;i < n;i ++)
{
scanf("%I64d",&lcm[i].c);
sumc += lcm[i].c * (i + 1);
}
if(sumw <= m)
{
printf("%I64d\n",sumc);
continue;
}
sort(lcm,lcm + n,cmp);
memset(bsum,0,sizeof(bsum));
for(i = n - 1;i >= 0;i --)
{
bsum[i] = lcm[i].num * lcm[i].c;
bsum[i] += bsum[i + 1];
}
flag = false;
ans = 0;
dfs(0,0,m);
printf("%I64d\n",ans);
}
return 0;
}
DFS(四):剪枝策略的更多相关文章
- hdoj--1010<dfs+奇偶剪枝>
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1010 题目描述:在n*m的矩阵中,有一起点和终点,中间有墙,给出起点终点和墙,并给出步数,在该步数情况 ...
- Entity FrameWork初始化数据库的四种策略
程序猿就是苦逼,每天还得分出一些时间去写博文.天真的很热,今天就随便写一点啦! 1.EF初始化数据库的四中策略 EF可以根据项目中的模型自动创建数据库.下面我们就分类看看Entity Framewor ...
- FastJson 支持配置的PropertyNamingStrategy四种策略
摘要: FastJson默认使用CamelCase,在1.2.15版本之后,FastJson支持配置PropertyNamingStrategy,支持四种策略: CamelCase.PascalCas ...
- hdu1010 Tempter of the Bone —— dfs+奇偶性剪枝
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1010 Tempter of the Bone Time Limit: 2000/1000 MS (Ja ...
- Sticks POJ - 1011 少林神棍 dfs四次剪枝
http://poj.org/problem?id=1011 题意:若干根棍子被截成小段的木棒,现在给你这些木棒,问最短可以拼出的棍子长度. 题解:搜索,dfs(r,m) 二个参数分别代表还剩r个木棒 ...
- HDU 1010 Tempter of the Bone(DFS+奇偶剪枝)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1010 题目大意: 输入 n m t,生成 n*m 矩阵,矩阵元素由 ‘.’ 'S' 'D' 'X' 四 ...
- 洛谷P4907【CYH-01】小奔的国庆练习赛 :$A$换$B$ $problem$(DFS,剪枝)
洛谷题目传送门 顺便提一下题意有一个地方不太清楚,就是如果输出No还要输出最少需要添加多少张牌才能满足要求.蒟蒻考完以后发现四个点Too short on line 2... 比较需要技巧的搜索 既然 ...
- 九度OJ 1091:棋盘游戏 (DP、BFS、DFS、剪枝)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1497 解决:406 题目描述: 有一个6*6的棋盘,每个棋盘上都有一个数值,现在又一个起始位置和终止位置,请找出一个从起始位置到终止位置代 ...
- dfs的剪枝优化
两个剪枝问题 1. 当两点的距离(需要走的步数)大于剩下的时间时 剪去 2.奇偶剪枝问题 如果起点到终点所需走的步数的奇偶性与时间奇偶性不同的时候 剪去 起点到终点步数的奇偶性的判断 首先 明确点的奇 ...
随机推荐
- 如何在Oracle 12C中Drop/Truncate多个分区 (Doc ID 1482264.1)
How to Drop/Truncate Multiple Partitions in Oracle 12C (Doc ID 1482264.1) APPLIES TO: Oracle Databas ...
- 安装oracle11g服务端
1.将oracle11g压缩包 解压到D盘根目录下 2.打开解压出来的文件夹,以管理员身份运行setup 3.警告弹框点击“是(Y)” 4.在此步骤中,可以提供您的电子邮件,以获取有关Oracle安全 ...
- Redis学习笔记(九、Redis总结)
1.Redis五大对象: 在Redis中有五大对象,分别是String.List.Hash.Set.Sorted Set. 这五大对象都有自己独特的编码方式,每个编码的实现都不一样,有自己独特的使用场 ...
- java之对象类型转换
基本数据类型之间的转换: 自动类型转换:小的数据类型可以自动转换成大的数据类型: 强制类型转换:可以把大的数据类型转换成小的数据类型:float = (float)32.0; public class ...
- CSS画一个三角形,CSS绘制空心三角形,CSS实现箭头
壹 ❀ 引 这两天因为项目工作较少,闲下来去看了GitHub上关于面试题日更收录的文章,毕竟明年有新的打算.在CSS收录中有一题是 用css创建一个三角形,并简述原理 .当然对于我来说画一个三角形是 ...
- 记一次Ubuntu19无法安装docker源
按照各大网站以及个人习惯我会使用下面这种方法添加Docker源: root@ubuntu:~# sudo add-apt-repository "deb [arch=amd64] https ...
- 《How Tomcat works》
容器是一个处理用户servlet请求并返回对象给web用户的模块. org.apache.catalina.Container接口定义了容器的形式,用四种容器:Engine(引擎),Host(主机), ...
- python进程基础点整理
操作系统 串行: 一个程序完完整整的执行完再执行下一个 并发: 看起来像是同时运行,其实就是程序间的切换频率比较快,看不出来 并行:真正的同时运行 多道技术 空间复用:共用一个内存条,多个进程相互隔离 ...
- C# show Environment property info name and value retrieve, Maximize the Console Window based on window resolution
using System.Reflection; static void ShowEnvironmentInfoDemo() { Type type = typeof(Environment); Pr ...
- 常用类-Excel-使用Aspose.Cells插件
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Xm ...