原文地址:https://www.jianshu.com/p/68a1dc82a96b
问题描述
据说,少林寺的镇寺之宝,是救秦王李世民的十三棍僧留下的若干根一样长的棍子。在民国某年,少林寺被军阀炮轰,这些棍子被炸成\(N\)节长度各异的小木棒。战火过后,少林方丈想要用这些木棒拼回原来的棍子。可他记不得原来到底有几根棍子了,只知道古人比较矮,且为了携带方便,棍子一定比较短。他想知道这些棍子最短可能有多短。
输入:\(N\)节木棒的长度。
输出:能拼成的最小的棍子长度。
解题思路
剪枝方案
通过剪枝来提高速度。
程序实现
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int N;//木棒总数目
int L;//棍子长度
const int MAXN = 101;
vector<int> stickLength;//存放各木棒长度
bool used[MAXN];//标记各木棒是否使用过
int totalLen;
int lastStickID;
bool DFS(int nLeftSticks, int nNeededLength)
{
if(nLeftSticks == 0 && nNeededLength == 0) return true;
if(nNeededLength == 0) nNeededLength = L;
//剪枝4:确保拼的部分从长到短排序
int nowStickID = 0;
if(nNeededLength < L)
nowStickID = lastStickID + 1;
for(int i = nowStickID; i < N; i++)
if((!used[i]) && stickLength.at(i) <= nNeededLength)
{
if(i > 0) //剪枝1:不要在同一个位置多次尝试相同长度的木棒
{
if((!used[i - 1]) && stickLength.at(i - 1) == stickLength.at(i))
continue;
}
used[i] = true;
lastStickID = i;
if(DFS(nLeftSticks - 1, nNeededLength - stickLength.at(i)))
return true;
else
{
used[i] = false;//下次可能会用到该木棒
if(nNeededLength == L || nNeededLength - stickLength.at(i) == 0) return false;
//剪枝2:一根棍子的第一个木棒不行就不要再尝试,
//因为该木棒终将出现在另一根棍子中,也拼不出来
//剪枝3:不要希望通过仅仅替换已经拼好棍子的最后一根木棒来拼成功
//因为该木棒终将出现在另一根棍子中,将其与替换其的短木棒组合进行交换也能成功拼成另一根棍子,矛盾
}
}
return false;
}
int main()
{
while(1)
{
cin >> N;
if(N == 0)
break;
int tmpLen;
memset(used, 0, sizeof(used));
stickLength.clear();
totalLen = 0;
for(int i = 0; i < N; i++)
{
cin >> tmpLen;
stickLength.push_back(tmpLen);
totalLen += tmpLen;
}
sort(stickLength.begin(), stickLength.end(), greater<int>());
for(L = stickLength.at(0); L <= totalLen / 2; L++)
{
if(totalLen % L)
continue;
if(DFS(N, L))
{
cout << L << endl;
break;
}
}
if(L > totalLen / 2) cout << totalLen << endl;
}
return 0;
}