拯救少林神棍

少林神棍

题意很简单就是有多跟木棍要拼成长度相等的的木棍,使得长度最小,输出可以拼成的木棒数量。

一看就是深搜的题目,但是直接暴力搜索会超时,自己写了好久也没过,早就听说这道题是神剪枝,用了好多剪枝,自己看了好久终于懂了。

首先找到长度最大的木棒,判断是否可以被总长度整除,不可以就+1,直到可以被总长度整除,暂时把这个长度作为目标长度进行深搜,到全部搜完得不到答案再求下一个可以被总长度整除的数,以此类推进行搜索。

越长的木棍对后面木棍的约束力越大,因此要把小木棍排序,按木棍长度从大到小搜索,这样就能在尽可能靠近根的地方剪枝。

(剪枝一)

    如果当前木棍能恰好填满一根原始木棍,但因剩余的木棍无法组合出合法解而返回,那么让我们考虑接下来的两种策略,一是用更长的木棍来代替当前木棍,显然这样总长度会超过原始木棍的长度,违法。二是用更短的木棍组合来代替这根木棍,他们的总长恰好是当前木棍的长度,但是由于这些替代木棍在后面的搜索中无法得到合法解,当前木棍也不可能替代这些木棍组合出合法解。因为当前木棍的做的事这些替代木棍也能做到。所以,当出现加上某根木棍恰好能填满一根原始木棍,但由在后面的搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。(剪枝二)

    显然最后一根木棍是不必搜索的,因为原始木棍长度是总木棍长度的约数。(算不上剪枝)

    考虑每根原始木棍的第一根木棍,如果当前枚举的木棍长度无法得出合法解,就不必考虑下一根木棍了,当前木棍一定是作为某根原始木棍的第一根木棍的,现在不行,以后也不可能得出合法解。也就是说每根原始木棍的第一根小木棍一定要成功,否则就返回。(剪枝四)

    剩下一个通用的剪枝就是跳过重复长度的木棍,当前木棍跟它后面木棍的无法得出合法解,后面跟它一样长度的木棍也不可能得到合法解,因为后面相同长度木棍能做到的,前面这根木棍也能做到。(剪枝五)

神剪枝代码:

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
vector<int> len(65);
int vis[65];//木棒是否已使用过
int L;
int n;
/*
*剩余rest根木棒时,当前棍子还差期望长度L的值为needLen时是否有搜索结果
*/
bool dfs(int rest,int needLen,int lastIndex)
{
    if(rest==0&&needLen==0)
      {
        return true;
       }
    if(needLen==0)
     {
        needLen=L;
    } //一根拼完拼新的一根,直到rest为0。
    //剪枝4:多根木棒组成一根木棍,让木棒按长度由大到小排列组成,
    //不必搜索当前木棒比上一根木棒长的情况,因为如果此种情况可以的话,只是相当于将组成情况不按长度大小排序,
    //不按长度大小排序可成功那么按长度大小排序也可成功。
    int startInex=needLen==L?0:lastIndex+1;
    for(int i=startInex;i<n;i++)
        {
        if(i>0&&!vis[i-1]&&len[i-1]==len[i])
        {
            continue;
        }//剪枝1:上次没拼成功,这次不尝试相同长度的木棒。
        if(!vis[i]&&len[i]<=needLen)
         {
            vis[i]=1;
            if(dfs(rest-1,needLen-len[i],i))
            {
                return true;
            }
            else
            {
                //没拼成功,换下一根。
                vis[i]=0;
                if(needLen==L||needLen==len[i])
                    {
                    return false;//剪枝2:len[i]为第一根木棒的情况下没拼成功,
                          //则这根木棒永远不能再被用上,所以不可能全部木棒被使用,
                         //后续搜索全部放弃。
                    //剪枝3:假设len[i]为最后一根木棒,被更小木棒替换后最终能够成功,
                    //那么3必然出现在后面的某个棍子k里。
                    //将棍子k中的len[i]和棍子i中用来替换3的几根木棒对调,
                    //结果当然一样是成功的。这就和i原来的拼法会导致不成功矛盾。
                }
            }
        }
    }
    return false;
}

int main()
{
    while(cin>>n&&n>0)
     {
        len.clear();
        int minLen=0;
        int totalLen=0;
        int tempLen;
        for(int i=0;i<n;i++)
        {
            cin>>tempLen;
            len.push_back(tempLen);
            totalLen+=tempLen;
        }
        sort(len.begin(),len.end(),greater<int>());//保持棒子长度由大到小
        for(L=len[0];L<=totalLen/2;L++)
         {
            if(totalLen%L)
            {
                continue;//能整除才能用上全部木棒
            }
            memset(vis,0,sizeof(vis));
            if(dfs(n,L,-1))
            {
                minLen=L;
                break;//找到最小长度即可
            }
        }
        if(L>totalLen/2) //减少一层复杂度。
        {
            minLen=totalLen;
          }//搜索不到组成2根及2根以上棍子的方法,则所有木棒只能组成一根棍子。
       cout<<minLen<<endl;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值