在民国某年,少林寺被军阀炮轰,这些棍子被炸成 N 节长度各异的小木棒
战火过后,少林方丈想要用这些木棒拼回原来的棍子
可他记不得原来到底有几根棍子了,只知道古人比较矮,且为了携带方便,棍子一定比较短
他想知道这些棍子最短可能有多短
·
·
枚举所有可能的棍子长度
从最长的那根木棒的长度一直枚举到木棒长度总和的一半
对每个假设的棍子长度,试试看能否拼齐若干根棍子
·
·
对于不是木棒总长度的因子的长度,可以直接否定,不需尝试
·
·
一根一根地拼棍子
如果拼好前i根棍子,结果发现第i+1根无论如何拼不成了
→推翻第i根的拼法,重拼第i根…..
直至有可能推翻第1根棍子的拼法
·
·
状态可以是一个二元组 (R, M)
R : 还没被用掉的木棒数目
M : 当前正在拼的棍子还缺少的长度
假设共有N节木棒,假定的棍子长度是L:
初始状态: (N, L)
终止状态: (0, 0)
·
·
·
·
·
如果某次拼接选择长度为S 的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要跳过所有长度为S 的木棒
·
·
可以考虑把木棒2, 3换掉重拼棍子i,但是把2, 3都去掉后,换1是没有意义的
因为假设替换后能全部拼成功,那么这被换下来的第一根木棒,必然会出现在以后拼好的某根棍子k中
那么我们原先拼第i根棍子时, 就可以用和棍子k同样的构成法来拼,照这种构成法拼好第i根棍子,继续下去最终也应该能够全部拼成功
这就是一种去重复性的搜索
·
·
假设替换3后最终能够成功,那么3必然出现在后面的某个棍子k里
将棍子k中的3和棍子i中用来替换3的几根木棒对调,结果当然一样是成功的
这就和i原来的拼法会导致不成功矛盾
·
·
木棒3 比木棒2长,这种情况的出现是一种浪费。
因为要是这样往下能成功,那么2, 3 对调的拼法肯定也能成功。
由于取木棒是从长到短的,所以能走到这一步,就意味着当初将3放在2的位置时,是不成功的
具体方法:
为此,要设置一个全局变量 nLastStickNo,记住最近拼上去的那条木棒的下标。
·
·
#include
#include
#include
#include
#include
using namespace std;
int N;
int L;
vector<int> anLength;
int anUsed[65]; //是否用过的标记
int i, j, k;
int nLastStickNo;
bool cmp(int a, int b)
{
return a > b;
}
int Dfs(int R, int M)
{
if (R == 0 && M == 0)
return true;
if (M == 0) //一根刚刚拼完
M = L; //开始拼新的一根
int nStartNo = 0;
if (M != L) //剪枝4
nStartNo = nLastStickNo + 1;
for (int i = nStartNo; i < N; i++)
{
if (!anUsed[i] && anLength[i] <= M)
{
if (i > 0)
{
if (anUsed[i - 1] == false
&& anLength[i] == anLength[i - 1])
continue; //剪枝1
}
anUsed[i] = 1; nLastStickNo = i;
if (Dfs(R - 1,M - anLength[i]))
return true;
else {
anUsed[i] = 0; //说明本次不能用第i根
//第i根以后还有用
if (anLength[i] == M || M == L)
return false; //剪枝3, 2
}
}
}
return false;
}
int main()
{
while (1) {
cin >> N;
if (N == 0)
break;
int nTotalLen = 0;
anLength.clear();
for (int i = 0; i < N; i++) {
int n;
cin >> n;
anLength.push_back(n);
nTotalLen += anLength[i];
}
sort(anLength.begin(), anLength.end(),cmp); //要从长到短进行尝试
for (L = anLength[0]; L <= nTotalLen / 2; L++) {
if (nTotalLen % L)
continue;
memset(anUsed, 0, sizeof(anUsed));
if (Dfs(N, L)) {
cout << L << endl;
break;
}
}
if (L > nTotalLen / 2)
cout << nTotalLen << endl;
} // while
return 0;
}
1)选择特定的搜索顺序
如果一个任务分为 A, B, C…..等步骤(先后次序无关)
要优先尝试可能性少的步骤
这样可以尽早的排除不可能的情况, 减少搜索量
·
2)要发现表面上不同,实质相同的重复状态
避免重复的搜索, 就上上文对于第一根和第二根棍子的更换
·
3)要根据实际问题发掘剪枝方案
废话。。。