搜索题目(剪枝)

洛谷P 1120 小木棍

题目大意:给出一堆一已知长度的木棍,它们是由一些木棍随意切割而来,之前的木棍长度一样,问之前木棍的最短长度。
这道题目方向比较明显,就是遍历所有可能的数值,进行 dfs 搜索,但是这个问题中有很多可以优化的细节,比如遍历的数值,这个一般从0到木棍的长度之和,但是这里面有许多是达不到的,所以只用遍历所有的因数即可。在对木棍进行深度搜索时,同时要注意运算顺序,从长木棍到短木棍的运算速度要比乱序快,因为短的木棍更加灵活,这个是在洛谷的题解上看到的,虽然原理很简单,但是被我忽视了,短的木棍如果都被拼完了,长的木棍肯定很难组合成长度正合适的木棍,所以要先从长的开始,这个时候搜索的起点就是当前木棍的位置的下一个,这样也可以减少相当的运算量。还有一点是对于长度重复的木棍的省略,因为如果这一个木棍不成功,那么接下来和它长度相同的木棍也就不用再进行深度搜索了,注意的是,这一点说的是在他之后的木棍,可能存在当前木棍成立,下一个木棍所需的短棍就用光的情况。最后一个也是我觉得最厉害的一个,这个是我确实没有考虑过的问题,就是可以用一个数组来标记用过的木棍,dfs回溯的时候再把标记删除,这样可以不用再使用 memset 函数,这点在dfs中可以减去很多不必要的运算。(看了很多题解,这些都被称之为剪枝)

#include<bits/stdc++.h>
using namespace std;
int n,m,a[66],next[66],cnt,sum,len;
bool used[66],ok; //used数组即优化5的vis数组,记录每根木棍是否用过;ok记录是否已找到答案。
bool cmp(int a,int b){return a>b;}
void dfs(int k,int last,int rest){ //k为正在拼的木棍的编号,last为正在拼的木棍的前一节编号,rest为该木棍还未拼的长度
    int i;
    if(!rest){ //未拼的长度为0,说明这根原始长棍拼完了,准备拼下一个
        if(k==m){ok=1; return;} //优化6,全部拼完并符合要求,找到答案,直接返回

        for(i=1;i<=cnt;i++) //找一个还没用的最长的木棍打头即可。反正要想全都拼接成功,每根木棍都得用上
            if(!used[i]) break;
        used[i]=1;
        dfs(k+1,i,len-a[i]);
        used[i]=0;
        if(ok) return; //优化6,找到答案就退出
    }
    //优化4,二分找第一个 木棍长度不大于未拼长度rest 的位置
    int l=last+1, r=cnt, mid;
    while(l<r){
        mid=(l+r)>>1;
        if(a[mid]<=rest) r=mid;
        else l=mid+1;
    }
    for(i=l;i<=cnt;i++){
        if(!used[i]){ //优化5,判断木棍是否用过
            used[i]=1;
            dfs(k,i,rest-a[i]);
            used[i]=0;
            if(ok) return; //优化6,找到答案就退出

            if(rest==a[i] || rest==len) return; //优化7
            i=next[i]; //优化3
            if(i==cnt) return;
        }
    }
    //到了这里,说明这时候拼不成当前这根原始木棍了,传回失败信息并修改之前拼的木棍
}
int main(){
    cin>>n;
    int d;
    for(int i=1;i<=n;i++){
        cin>>d;
        if(d>50) continue;
        a[++cnt]=d;
        sum+=d;
    }
    sort(a+1,a+cnt+1,cmp); //优化1,木棍按长度从大到小排序
    //优化3,预处理next数组
    next[cnt]=cnt;
    for(int i=cnt-1;i>0;i--){
        if(a[i]==a[i+1]) next[i]=next[i+1];
        else next[i]=i;
    }
    for(len=a[1];len<=sum/2;len++){ //枚举原始长度
        if(sum%len!=0) continue; //如果不能拼出整数根 就跳过
        m=sum/len; //优化6中的那个计算
        ok=0;
        used[1]=1;
        dfs(1,1,len-a[1]);
        used[1]=0;
        if(ok){printf("%d\n",len); return 0;} //优化6,输出答案,退
    }
    printf("%d\n",sum); return 0;
}

这个是在洛谷看到的一个题解,但是他应用了二分来找下一根合适的的木棍,而且遍历的范围是按从零到总长的一半来的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值