167 木棒(dfs之剪枝)

1. 问题描述:

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。

输入格式

输入包含多组数据,每组数据包括两行。第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。

输出格式

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围

数据保证每一节木棍的长度均不大于 50。

输入样例:

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

输出样例:

6
5
来源:https://www.acwing.com/problem/content/description/169/

2. 思路分析:

这道题目属于经典的dfs剪枝的问题,题目中涉及到的剪枝比较多,对于dfs搜索的问题首先需要考虑如何搜索才可以将所有方案枚举出来,也即考虑搜索的顺序,其中一个可行的搜索顺序为:我们每一组每一组木棒进行拼接,当我们拼接好了一根木棒之后然后拼接下一根,这样一定可以将所有方案枚举出来,第二个问题是考虑如何对dfs进行剪枝优化,对于这道题目来说有以下几个剪枝优化:

  • 因为需要求解的是每一根木棒的最小长度,所以我们可以从长度较小的木棒开始搜索,并且需要满足一个条件:总的木棒长度是当前木棒长度的倍数才可以搜索;
  • 优化搜索顺序,我们可以从比较长的木棍开始枚举,这样每一次可以选择拼接当前木棒的木棍就比较少,也即搜索的分支比较少,当我们找到了一组合法解之后层层返回True即可
  • 排除等效冗余,① 顺序不同但是他们应该属于同一种方案,例如1,2,3与3,2,1对应的方案都是一样的,所以我们需要避免重复搜索,按照组合的方式进行搜索,一般可以在递归的方法中传递一个参数start参数,表示当前开始搜索的位置,这样对于排列顺序不一样的方案只会被搜一次;② 如果当前木棍拼接当前这一根木棒的时候失败了,则直接跳过后面所有长度相等的木棍,因为如果当前木棍放到后面的木棒中成功了,那么我们可以交换一下这两根长度相等的木棍那么就可以得到一个合法解与当前这一根木棒拼接到当前的位置失败就矛盾了,所以后面长度相等的也会拼接失败所以直接跳过长度相等的木棍即可;③ 如果是木棒中的第一根木棍失败则一定失败,因为失败了所以当前的木棍一定是接到了后面的某一个根木棒中,如果后面拼接成功了,那么我们可以交换拼接成功的木棒与当前的木棒那么就可以得到一个合法解与当前失败就矛盾了;④ 如果是木棒中的最后一根木棍失败也一定会失败;与第一根木棍失败是类似的,将失败的这一根与后面拼接成功的若干根交换一下也可以得到一个合法方案与失败就矛盾了;

3. 代码如下:

from typing import List


class Solution:
    #  u表示当前拼接的是哪一根木棒, curLen表示当前木棒拼接的长度, start表示从哪一个位置开始递归, s表示所有木棒的总和, length表示每一根木棒的长度
    def dfs(self, u: int, curLen: int, start: int, n: int, st: List[int], w: List[int], s: int, length: int):
        if u * length == s:
            return True
        if curLen == length:
            return self.dfs(u + 1, 0, 0, n, st, w, s, length)
        i = start
        while i < n:
            if st[i] == 1 or curLen + w[i] > length:
                i += 1
                continue
            st[i] = 1
            if self.dfs(u, curLen + w[i], i + 1, n, st, w, s, length): return True
            # 回溯
            st[i] = 0
            # 开头或者是结尾失败了直接返回False即可
            if curLen == 0 or curLen + w[i] == length: return False
            j = i
            # 跳过长度相等的木棍
            while j < n and w[j] == w[i]: j += 1
            i = j - 1
            i += 1
        # 执行到这里还没有找到合法解直接返回即可
        return False

    def process(self):
        while True:
            n = int(input())
            if n == 0: break
            # 优化搜索顺序, 从分支数量较少的木棍开始枚举
            w = list(map(int, input().split()))
            w.sort(reverse=True)
            # 当前是每一根木棒的长度
            length = 1
            s = 0
            for x in w:
                s += x
            st = [0] * (n + 10)
            while length <= s:
                # 从长度较小的开始枚举, 判断是否构成合法方案, 找到了就直接退出
                if s % length == 0 and self.dfs(0, 0, 0, n, st, w, s, length):
                    print(length)
                    break
                length += 1


if __name__ == "__main__":
    Solution().process()

猜你喜欢

转载自blog.csdn.net/qq_39445165/article/details/121786680
167