​少林神棍​

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

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

首先找到长度最大的木棒,判断是否可以被总长度整除,不可以就+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;
}