POJ 1011 Sticks(DFS回溯剪枝)

本文介绍了解决POJ1011 Sticks问题的方法,通过深度优先搜索(DFS)和有效的剪枝策略找到切割原始棍子的最小长度,确保能够完全覆盖所有给定的小棍子长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

POJ 1011 Sticks(DFS回溯剪枝)

http://poj.org/problem?id=1011

题意:

        给你N根长度可能不同的棍子,它们是由多根(或1根)长棍子切断而成的。现在要你求出原始棍子的最小合法长度是多少?

分析:

        本题是POJ2362的加强版:

http://blog.youkuaiyun.com/u013480600/article/details/27581015

        用类似的做法,首先我们求出所有小棍子的长度和sum.然后原始棍子的长度L肯定要>=最长的当前棍子长.

        然后让L从小到大递增(L<=sum),一一尝试.当sum%L==0时,可以dfs尝试.

        其中用vis[i]表示第i根棍子是否被使用了,用

        bool dfs(intcur_len, int cnt, int left, int begin)来尝试构造.其中cur_len表示本轮尝试我们假定的原始棍长,cnt表示我们正在尝试构造第cnt根原始棍,left表示当前第cnt棍子我们还差left长度需要构造,begin表示小棍子从begin位置的小棍子开始探索(关键的剪枝就在这里,定序技巧,POJ2362我解释了)

        源代码中共有5处剪枝,少了一处都可能超时.关键在于剪枝4那里,小木棍一定要从大到小排序.(从小到大排序超时) 主要原因在于:

        剪枝3那里,如果一根小木棍作为当前原始木棍的第一根都没有后续匹配方案的话,那么这个小木棍不可能出现在剩下的任何方案中(想想,是不是).这里如果当前剩余小木棍长度为1,1,1,1,1,1,8. 我们需要配对的原始木棍长度为10时,那么就要递归6次才能判断出.如果小木棍长度从大到小排序的话如:8 1 1 1 1 1 1 ,那么只需要递归3次就能判断出.所以长的小木棍收敛速度更快,所以需要木棍长度从大到小排序再DFS.

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=70;
int n,sum;
int len[maxn];
bool vis[maxn];
bool cmp(int a,int b)
{
    return a>b;
}
bool dfs(int cnt,int cur_len,int left,int begin)//当前构造第cnt根棍子,原始长度cur_len,还需left长度每构造,从begin位置开始选小棍子
{
    if(cnt==sum/cur_len) return true;//正好构造完sum/cur_len根原始木棍
    int fail=-1;//记录上一轮失败木棍的长度
    for(int i=begin;i<n;i++)
    {
        if(vis[i] || fail==len[i]) continue; //剪枝1,fail长度已经失败一次,不用再试
        vis[i]=true;
        if(left==len[i])
        {
            if(dfs(cnt+1,cur_len,cur_len,0)) return true;
            fail=len[i];
        }
        else if(left>len[i])
        {
            if(dfs(cnt,cur_len,left-len[i],i+1)) return true; //剪枝2,定序剪枝
            fail=len[i];
        }
        vis[i]=false;
        if(left==cur_len) break;//剪枝3,此时的用len[i]的小木棍作为原始棍的第一根,
                                //都不能组合成一根原始棍,说明len[i]不论放哪里都是没用的,后面不用再试
    }
    return false;
}
int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        sum=0;
        int max_len=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&len[i]);
            max_len=max(max_len,len[i]);
            sum+= len[i];
        }
        sort(len,len+n,cmp);//剪枝4,这里一定要从大到小排序,如果从小到大就超时,与剪枝3有关
        int L;
        bool flag=false;
        for(L=max_len;L<=sum-L;L++)if(sum%L==0)//剪枝5,循环只需找到sum/2的下取整即可
        {
            memset(vis,0,sizeof(vis));
            if(dfs(0,L,L,0))
            {
                printf("%d\n",L);
                flag=true;
                break;
            }
        }
        if(flag==false) printf("%d\n",sum);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值