DFS +剪枝(经典例题)

描述

乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棍的长度都不超过50个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。

输入

输入包含多组数据,每组数据包括两行。第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。

输出

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

样例输入

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

样例输出

6
5

 

[解】:下面说下几个重要的剪枝:
1.把所有木棍的长度从大到小排列,组合木棒时优先使用长的木棍,这样可以加快组合速度,并且对后面的剪枝有帮助。
2.木棒的长度一定是大于等于最长木棍的长度并且小于等于所有木棍长度的和,这个很容易证明。
3.木棒的长度一定是所有木棍长度的和的约数,这个也很容易证明。
4.在某一个木棒的组合过程中,对于当前的木棍stick[i],如果stick[i-1]没有被组合并且stick[i] == stick[i-1],那么不用考虑stick[i],显然stick[i]最终也不会被组合。
5.如果此次是在尝试第i个木棒的第一段,假设stick[j]为当前可以被使用的最长的木棍,如果此次组合失败,直接退出搜索,即退回到对第i-1个木棒的搜索。试想:失败说明现在使用stick[j]是不可行的,那么以后无论什么时候使用stick[j]都是不可行的,因为以后再处理stick[j]时可使用的木棍一定是当前可使用的木棍的子集,在更多木棍选择的情况下都不能组合成功,那么,在更少木棍选择的情况下一定不能组合成功。

下面就是关键的了,就是这道题dfs的实现和剪枝的设计:
1.以一个小棒为开头,用dfs看看能否把这个小棒拼凑成len长,如果可以,用visit[i]记录下用过的小棒,然后继续以另外一个小棒为开头,以此类推。
2.小棒的长度从大到小排序,这个就不解释了。
3.如果当前最长的小棒不能拼成len长,那么以比它短的开始也一定不能取得全局的成功。因为每一支木棍都要被用到,最长的那根如果现在不行,那么以后也不行。所以直接return,返回前一步。
4.最重要的,就是比如说17,9,9,9,9,8,8,5,2……如果当前最长小棒为17,它与第一个9组合之后dfs发现不能拼成len,那么17就不用和后面所有的9组合了,而直接和8开始组合。这个剪枝直接从TLE到16MS,很强大。

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace  std;
bool isEnd=false;
int len=1;
vector<int>  a(64);
vector<int>  visit(64);
int num;
//used_num为当前已被用过的小棒数,now_index为当前要处理的小棒。

void  dfs(int used_num,int now_len,int now_index){
    if(isEnd==true){
       return;
     }
    if(now_len==len){
        if(used_num==num){
            isEnd=true;
        }else{
            dfs(used_num,0,0);
        }
        return;
    }
    if(now_len==0){//当前长度为0,寻找下一个当前最长小棒
       while(visit[now_index]==1) now_index++;
       visit[now_index]=1;
       dfs(used_num+1,a[now_index],now_index+1);
       visit[now_index]=0;
    }else{
       for(int i=now_index;i<num;i++){
           if((visit[i]==0)&&(now_len+a[i])<=len){
               if(visit[i-1]==0&&a[i]==a[i-1]){//不重复搜索:最重要的剪枝
                   continue;
               }
               visit[i]=1;
               dfs(used_num+1,now_len+a[i],i+1);
               visit[i]=0;
           }
       }
    }
}
bool cmp(int x,int y){
    return x>y;
}
int main()
{
     while(cin>>num){
         if(num==0){
             break;
         }
         isEnd=false;
         int sum=0;
         int max=0;
         for(int i=0;i<num;i++){
             cin>>a[i];
             sum+=a[i];
             if(a[i]>max){
                 max=a[i];
             }
         }
         sort(a.begin(),a.end(),cmp);
         for(len=max;len<sum;len++){
             if(sum%len!=0){
                 continue;
             }else{
                 dfs(0,0,0);
                 if(isEnd==true){
                     break;
                 }
             }
         }
         cout<<len<<endl;
     }
    return 0;
}

题目描述

问题描述:给出4个1-10的数字,通过加减乘除,得到数字为24就算胜利
输入:
4个1-10的数字。[数字允许重复,但每个数字仅允许使用一次,测试用例保证无异常数字]
输出:
true or false

输入描述:

输入4个int整数

输出描述

返回能否得到24点,能输出true,不能输出false

示例1

输入

复制

7 2 1 10

输出

复制

true
#include<stdio.h>
#include<stdlib.h>
#include <vector>
#include <iostream>
using namespace std;

int dfs(vector<int> num,vector<int> flag,int sum)
{
    if(sum==24)
    {
       return 1;
    }
    for(int i=0;i<4;i++)
    {
        if(flag[i]==0)
        {
            flag[i]=1;
            if(dfs(num,flag,sum+num[i])==1)
            {
                return 1;
            }
            if(dfs(num,flag,sum-num[i])==1)
            {
                return 1;
            }
            if(dfs(num,flag,sum*num[i])==1)
            {
                return 1;
            }
            if(num[i]!=0&&dfs(num,flag,sum/num[i])==1&& sum % num[i]==0)
            {
                return 1;
            }
            flag[i]=0;
        }
    }
    return 0;
}
int main(){
    vector<int>  flag(4,0);
    vector<int>  num(4,0);
    while(cin>>num[0]>>num[1]>>num[2]>>num[3]){
        int flag1=dfs(num,flag,0);
        if(flag1==1)printf("true");
        else printf("false");
        printf("\n");
    }
    return 0;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值