1118 分成互质组(dfs之搜索顺序)

1. 问题描述:

给定 n 个正整数,将它们分组,使得每组中任意两个数互质。至少要分成多少个组?

输入格式

第一行是一个正整数 n。第二行是 n 个不大于10000的正整数。

输出格式

一个正整数,即最少需要的组数。

数据范围

1 ≤ n ≤ 10

输入样例:

6
14 20 33 117 143 175

输出样例:
3
来源:https://www.acwing.com/problem/content/description/1120/

2. 思路分析:

分析题目可以知道我们需要搜索出所有的质数划分方案,在所有划分方案中求解出划分的最小组数,我们需要想一种枚举顺序将所有的方案枚举出来,第一种枚举顺序:对于每一个数字我们尝试将其放入到之前的组别中,如果之前有某一组可以放入当前的数字那么将其放入到对应的组别中,或者是可以新开一个组来放当前的数字(这个操作是任何时候可以执行的),这样一定可以枚举出所有的划分方案。第二种枚举顺序:我们可以一个组一个组进行枚举,尝试将当前的数字放入到最后一个组中,其中对应两个决策方式:

  • 把某个数字加入到最后一组中,如果可以加那么就加,每一次是将当前能够加的数字放入到最后一组中
  • 新开一个组(任何时候都可以做)

对于第二种枚举顺序其实可以做两个优化:当前数字p[i]可以放到最后一组中那么就不用新开一组,在实现的时候可以使用一个flag标记判断是否可以放入到最后一组,因为是一组一组搜索的,而且是没有顺序要求的,例如2,5,7和2,7,5是一样的,也即搜索的是组合的顺序而不是排列的顺序,我们在搜索的时候可以规定一个顺序,也即从小到大的位置搜索,在实现的时候可以传递一个start遍历表示当前开始搜索的位置。

但是第二种方法会超时,第一种则不会超时。

3. 代码如下:

枚举每一个数字应放入到哪一组中:

from typing import List


class Solution:
    res = 0
    
    # 计算a和b的最大公约数
    def gcd(self, a: int, b: int):
        return a if b == 0 else self.gcd(b, a % b)
    
    # 检验当前这一组是否可以放数字t
    def check(self, groups: List[int], t: int):
        for i in range(len(groups)):
            if self.gcd(groups[i], t) > 1: return False
        return True

    # u表示当前搜索的是第u个数字, n表示总的数字的数量, g表示当前有几组, groups存储每一组中的质数
    def dfs(self, u: int, n: int, g: int, p: List[int], groups: List[List[int]]):
        # 最优性剪枝, 如果发现当前搜下去一定小于之前搜到的最优解直接返回即可
        if g >= self.res: return 
        # 搜到了最后一个数字
        if u == n:
            # 走到了这里说明g小于答案, 直接赋值即可
            self.res = g
            return
        for i in range(g):
            if self.check(groups[i], p[u]):
                # 当前组别可以放当前的元素那么将当前的元素放到当前的组中
                groups[i].append(p[u])
                self.dfs(u + 1, n, g, p, groups)
                # 回溯
                groups[i].pop()
        # 尝试新开一个组求解划分的组数, 看是否可以求得更小, 新来一个组的时候可能搜到的答案更小:4
        # 3 7 6 14
        groups[g].append(p[u])
        self.dfs(u + 1, n, g + 1, p, groups)
        # 回溯
        groups[g].pop()

    # 枚举放到哪一个组, 如果可以放到之前的组别那么直接放到对应的组别中, 或者是新开一个组来放当前的数字
    def process(self):
        n = int(input())
        p = list(map(int, input().split()))
        self.res = n
        groups = [list() for i in range(n + 10)]
        self.dfs(0, n, 0, p, groups)
        return self.res


if __name__ == "__main__":
    # 测试数据: 6 
    # 2 4 6 7 9 10
    print(Solution().process())

第二种写法(超时):

from typing import List


class Solution:
    res = 0

    # 求解a和b的最大公约数
    def gcd(self, a: int, b: int):
        return a if b == 0 else self.gcd(b, a % b)

    def check(self, group: List[int], gc: int, t):
        for i in range(gc):
            # 说明group中存在与t不是互质的数字
            if self.gcd(group[i], t) > 1: return False
        return True
    
    # n为总的数字个数, g为当前的组数, gc为当前组数中有多少个数字, tc表示当前已经划分好多少个数字了, start表示当前开始搜索的位置, p为所有数字, group存储每一组有哪些数字
    def dfs(self, n: int, g: int, gc: int, tc: int, start: int, p: List[int], st: List[int], group: List[List[int]]):
        if g >= self.res: return
        if tc == n: self.res = g
        # flag标记用来优化, 当前数字可以加到最后一组那么就不用新开一组
        flag = 1
        for i in range(start, n):
            # 检验最后一组是否可以放当前的数字
            if st[i] == 0 and self.check(group[g], gc, p[i]):
                st[i] = 1
                group[g][gc] = p[i]
                self.dfs(n, g, gc + 1, tc + 1, i + 1, p, st, group)
                st[i] = 0
                flag = 0
        if flag: self.dfs(n, g + 1, 0, tc, 0, p, st, group)

    def process(self):
        n = int(input())
        p = list(map(int, input().split()))
        group = [[0] * 11 for i in range(11)]
        st = [0] * (n + 10)
        self.res = n
        self.dfs(n, 1, 0, 0, 0, p, st, group)
        return self.res


if __name__ == '__main__':
    print(Solution().process())

Guess you like

Origin blog.csdn.net/qq_39445165/article/details/121737028