顾名思义,剪枝就是通过一些判断,剪掉搜索树上不必要的子树。在采用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.最优性剪枝

最优性剪枝,又称为上下界剪枝,是一种重要的剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。
      对于求最优解的一类问题,通常可以用最优性剪枝。比如在求迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算。

【例3】The Robbery (POJ 3900)。
Description
In the downtown of Bucharest there is a very big bank with a very big vault. Inside the vault there are N very big boxes numbered from 1 to N. Inside the box with number k there are k very big diamonds, each of weight Wk and cost Ck. 
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

The first line contains single integer T – the number of test cases. Each test case starts with a line containing two integers N and M separated by a single space. The next line contains N integers Wk separated by single spaces. The following line contains N integers Ck separated by single spaces.

Output

For each test case print a single line containing the maximal possible total cost of diamonds.

Sample Input

2
2 4
3 2
5 3
3 100
4 7 1
5 9 2

Sample Output

6
29

Hint

Constraints: 
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(四):剪枝策略的更多相关文章

  1. hdoj--1010<dfs+奇偶剪枝>

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1010 题目描述:在n*m的矩阵中,有一起点和终点,中间有墙,给出起点终点和墙,并给出步数,在该步数情况 ...

  2. Entity FrameWork初始化数据库的四种策略

    程序猿就是苦逼,每天还得分出一些时间去写博文.天真的很热,今天就随便写一点啦! 1.EF初始化数据库的四中策略 EF可以根据项目中的模型自动创建数据库.下面我们就分类看看Entity Framewor ...

  3. FastJson 支持配置的PropertyNamingStrategy四种策略

    摘要: FastJson默认使用CamelCase,在1.2.15版本之后,FastJson支持配置PropertyNamingStrategy,支持四种策略: CamelCase.PascalCas ...

  4. 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 ...

  5. Sticks POJ - 1011 少林神棍 dfs四次剪枝

    http://poj.org/problem?id=1011 题意:若干根棍子被截成小段的木棒,现在给你这些木棒,问最短可以拼出的棍子长度. 题解:搜索,dfs(r,m) 二个参数分别代表还剩r个木棒 ...

  6. HDU 1010 Tempter of the Bone(DFS+奇偶剪枝)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1010 题目大意: 输入 n m t,生成 n*m 矩阵,矩阵元素由 ‘.’ 'S' 'D' 'X' 四 ...

  7. 洛谷P4907【CYH-01】小奔的国庆练习赛 :$A$换$B$ $problem$(DFS,剪枝)

    洛谷题目传送门 顺便提一下题意有一个地方不太清楚,就是如果输出No还要输出最少需要添加多少张牌才能满足要求.蒟蒻考完以后发现四个点Too short on line 2... 比较需要技巧的搜索 既然 ...

  8. 九度OJ 1091:棋盘游戏 (DP、BFS、DFS、剪枝)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1497 解决:406 题目描述: 有一个6*6的棋盘,每个棋盘上都有一个数值,现在又一个起始位置和终止位置,请找出一个从起始位置到终止位置代 ...

  9. dfs的剪枝优化

    两个剪枝问题 1. 当两点的距离(需要走的步数)大于剩下的时间时 剪去 2.奇偶剪枝问题 如果起点到终点所需走的步数的奇偶性与时间奇偶性不同的时候 剪去 起点到终点步数的奇偶性的判断 首先 明确点的奇 ...

随机推荐

  1. 配置 yum 源的两种方法

    配置 yum 源的两种方法 由于 redhat的yum在线更新是收费的,如果没有注册的话不能使用,如果要使用,需将redhat的yum卸载后,重启安装,再配置其他源,以下为详细过程:  1.删除red ...

  2. 如何使用python远程操作linux

    在云服务测试中,往往需要我们进入云服务内容进行相关内容的测试.这测试可以使用平台自身的noVNC.外部辅助xshell等工具连接到云服务内部进行测试.但是在如此反复的测试操作中,就需要用到自动化测试方 ...

  3. 《Web Development with Go》Mangodb查询collection内所有记录

    相当于select * from table; package main import ( "fmt" "log" "time" " ...

  4. java之模板方法设计模式

    抽象类体现的就是一种模板模式的设计.抽象类作为多个子类的通用模板.子类在抽象类的基础上进行扩张和改造,但子类总体上会保留抽象类的行为方式. 解决的问题: 当功能内部一部分实现是确定的,一部分实现是不确 ...

  5. PHP 将某个http地址的远程图片下载到本地的某个目录

    代码: function getImage($url,$save_dir='',$filename='',$type=0){ if(trim($url)==''){ return array('fil ...

  6. jupyter notebook改变行间图片大小

    jupyter notebook使用起来代码效果很直接,这是我最喜欢的一点,但是主题单一,后来改了一下主题.也可以接受了,但是还有一个问题是显示图片太小我们可以用两个方法来改变它. 一.可以通过rcP ...

  7. UTXO和Account模型一个都不能少

    UTXO对于非区块链从业人员来说可能比较陌生,UTXO的全称是Unspent Transaction Output,这中本聪在比特币中的一个天才设计.而Account模型就很常见,也很容易理解,你银行 ...

  8. javascript ES6 新特性之 解构

    解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值 var arr = [1, 2, 3]; //传统方式 var a = arr[0], ...

  9. 什么是BGP协议

    Border Gateway Protocol,边界网关协议,简称BGP,主要用于互联网AS(自治系统)之间的互联. Linux内核原生支持的.专门用在大规模数据中心维护不同的"自治系统&q ...

  10. goweb-搭建服务

    web应用简介 Web 应用在我们的生活中无处不在.看看我们日常使用的各个应用程序,它们要 么是 Web 应用,要么是移动 App 这类 Web 应用的变种.无论哪一种编程语言,只要 它能够开发出与人 ...