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.奇偶剪枝问题 如果起点到终点所需走的步数的奇偶性与时间奇偶性不同的时候 剪去 起点到终点步数的奇偶性的判断 首先 明确点的奇 ...
随机推荐
- dedecmsV5.7 后台上传m4a的音频之后不展示
问题:dedecmsV5.7 在后台上传了m4a的音频文件(如何添加m4a的音频格式,更改系统-系统基本配置-附件设置)之后,列表里不展示,如图: 解决方案: 打开include/dialog/sel ...
- PWA 学习笔记(一)
PWA 介绍 概念: PWA(Progressive web apps,渐进式 Web 应用)运用现代 Web API 和传统的渐进式增强策略来创建跨平台 Web 应用程序 它并不是一个快捷方式,而能 ...
- Could not open requirements file: [Errno 2] No such file or directory: 'requirements.txt
最近安装python,已经安装好,cmd终端中输入python.pip等命令都有用 然而在配置requirements.txt文件过程中,执行语句 “pip install -r requiremen ...
- R-4 方差分析
本节内容: 1:方差分析的原理 2:单因数方差分析 .双因数分析 3:交互项 一:方差分析是原理 方差分析原理 对总体均值的假设检验,有三种情况:1.总体均值与某个常数进行比较:2.两个总体均值之间的 ...
- 如何用css实现太极图
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>太 ...
- C sharp #004# 进度条与Timer
饮水思源:金老师的自学网站 C#实现进度条异常简单,因为所有东西都已经封装好了. 只需要简单的拖拽: 写两行代码就完工了: private void timer1_Tick(object sender ...
- PAT 1010 Radix 进制转换+二分法
Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 110 be true? The an ...
- [错误]Caused by: org.apache.spark.memory.SparkOutOfMemoryError: Unable to acquire 65536 bytes of memory, got 0
今天,在运行Spark SQL代码的时候,遇到了以下错误: Caused by: org.apache.spark.SparkException: Job aborted due to stage f ...
- [WPF 自定义控件]让Form在加载后自动获得焦点
1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录"对话框加载后"用户名"应该马上获得焦点,用 ...
- Wappalyzer(chrome网站分析插件)
Wappalyzer是一款功能强大的.且非常实用的chrome网站技术分析插件,通过该插件能够分析目标网站所采用的平台构架. 网站环境.服务器配置环境.JavaScript框架.编程语言等参数,使用时 ...