题意:你有一组等长的木棒,现在你随机地砍断它们得到若干根小木棍,且每一根小木棍的长度小于等于 5050。现在你想把这些木棍拼接起来恢复到初始状态,但是你忘记了初始时有多少根木棒和木棒长度,问木棒的可能最小长度,保证所有所给的长度均为大于 00 的整数。

解析:一道搜索模板题。我们首先枚举木棒的长度 lenlen,它应该是木棍长度总和 sumsum 的约数,然后再搜索每一根木棒应该用哪些木棍拼接起来。每一个状态包括三个参数:num,now,lastnum,now,last,分别记录正在拼第几根木棒,正在拼的木棒已经拼了多长,上一根用来拼的小木棍,每个状态枚举未用的木棍中选择一个尝试来拼接来递归到新状态,直到拼完所有 sumlen\dfrac{sum}{len} 根。

现在我们需要考虑如何剪枝。目前常用的剪枝方法有这几种:

  1. 优化搜索顺序

    就是将搜索的顺序变成能让程序跑得更快的顺序,一般对其进行有序化处理

  2. 排除等效冗余

    若在搜索中产生了分支,其中有几条分支产生的结果相同产生的效果相等时,只用搜索其中一条分支就过了,不必搜索其他分支;

  3. 可行性剪枝

    若在进行搜索一条分支时,如果发现这条分支无法到达递归边界,直接回溯;

  4. 最优性剪枝

    在剪枝过程中,如果发现目前的费用已经大于已搜索到的最优费用,直接回溯;

  5. 记忆化

    对于每次搜索到的结果进行存储,避免搜索到重复或多余的结果。

这道题我们来考虑以上剪枝:

  1. 首先考虑能否优化搜索顺序。我们把小木棍长度从大到小排序,即开始尽可能拼更长的木棍,这样能更快拼完;

  2. 接着是排除等效冗余。可以分析出以下几点相同的分支:

    • 任意拼两根小木棍时,先后顺序是和结果无关的。我们可以限制只搜索其中一种顺序,即规定小木棍的拼接顺序按照小木棍长度递减,这里利用了拼接顺序的等效性;
    • 我们记录了上一根用来拼接的小木棍 lastlast,如果用它拼接搜索失败,我们就不用拼接和这根小木棍长度相同的小木棍,这里利用了木棍等长的等效性;
    • 如果在当前拼的木棒中,尝试拼入的第一根木棍的递归分支就搜索失败,那么直接判定当前分支失败。因为在拼入这根木棍前所有待拼的木棒都是空的,不管选择拼哪根木棒都会失败,这里利用了空木棒的等效性;
    • 如果在当前拼的木棒中,拼入这跟木棍恰好能拼接完整这跟木棒,但接下来拼接剩余原始木棒的递归分支失败,那么直接返回当前分支失败。因为用 11 根木棍恰好拼完当前木棒必然比用多根木棍拼完当前木棒更好,且两者是等效的。这里利用了贪心思想。
  3. 接着是可行性剪枝。我们不能在拼接过程中直接判断是否可行,所以没用到这个方法;

  4. 然后是最优性剪枝。这道题并没有求最优方案,也不会用到这个方法;

  5. 最后是记忆化。这道题不会搜索到之前有过的状态,所以不必记忆化。

综上,我们用前 22 个剪枝即可通过这道题。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[101],d[101],n,cnt,len,sum,v;
bool dfs(int num,int now,int last)
{
    if(num>cnt)
        return true;
    if(now==len)
        return dfs(num+1,0,1);
    int flag=0;//用于记录长度相同的木棍,剪枝2.2
    for(int i=last;i<=n;i++)//剪枝2.1
    {
        if(!d[i]&&now+a[i]<=len&&flag!=a[i])
        {
            d[i]=1;
            if(dfs(num,now+a[i],i+1))
                return true;
            flag=a[i];
            d[i]=0;
            if(now==0||now+a[i]==len)//剪枝2.3,2.4
                return false;
        }
    }
    return false;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
        v=max(v,a[i]);
    }
    sort(a+1,a+n+1);//剪枝1
    reverse(a+1,a+n+1);
    for(len=v;len<=sum;len++)//枚举长度
    {
        if(sum%len)
            continue;
        cnt=sum/len;//多少根木棒
        memset(d,0,sizeof(d));
        if(dfs(1,0,1))
            break;
    }
    printf("%d",len);
    return 0;
}