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.奇偶剪枝问题 如果起点到终点所需走的步数的奇偶性与时间奇偶性不同的时候 剪去 起点到终点步数的奇偶性的判断 首先 明确点的奇 ...
随机推荐
- Java学习笔记(9)--- 重写,重载,多态
1.重写: a.定义: 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为. 也就是说子类能 ...
- AXN文档
https://help.aliyun.com/document_detail/59705.html?spm=a2c4g.11186623.6.664.58a053afCvMM57 AXN api文档 ...
- 关于input标签不同type下的盒模型
刚才发现,在Chrome下input标签的不同type类型所取的盒模型是不一样的.浪费了我很多时间去调试,唉. type="text"时,给它设置宽度width:300px,此时的 ...
- Java连载55-Mail编程
一.电子邮件的历史 1.起源: 1969 Lenoard K. 教授发给同事的“LO” 1971 美国国防部自主的阿帕网(Arpanet)的通讯机制 通讯地址里用@ 1987年中国的第一份电子邮件 “ ...
- Spring Boot MVC api返回的String无法关联到视图页面
1:问题 使用 @Restcontroller 返回值定义为String 时 无法返回具体的页面 @RestController public class HelloController { @Get ...
- ycsb 测试Hbase性能
下载 github:https://github.com/brianfrankcooper/YCSB/releases/tag/0.10.0 wget https://github.com/brian ...
- 什么是单点登录,php是如何实现单点登录的
单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任.单点登录在大型网站里使用得 ...
- PHP程序执行的过程原理
为了以后能开发PHP扩展,就一定要了解PHP的执行顺序.这篇文章就是为C开发PHP扩展做铺垫. Web环境我们假设为Apache.在编译PHP的时候,为了能够让Apache支持PHP,我们会生成一个m ...
- 微信公众号支付提示当前页面的URL未注册
问题: 记一下前端时间自己做了一个微信公众号支付的功能,因为有一段时间没有接触过了微信支付方面的开发,居然忘记了在微信商户商户号中配置了对应的支付目录,所以提示我当前的域名是没有注册的. 设置支付目录 ...
- 移除 DevExpress 的 XtraForm 标题文字阴影
问题 在使用 DevExpress 开发 WinForm 程序时,我是使用的默认皮肤进行开发.但客户要求标题栏背景色改为蓝色,标题文字颜色改为白色. 改颜色比较简单,参考了 DevExpress Su ...