1617. 统计子树中城市之间最大距离

题目描述

有n个城市,编号是1-n,
给了一个数组edges表示了城市的连接关系,且每个城市间最多只有一条路(城市间的连接是树),
需要求一个结果ans,其中ans[i]表示城市间最大距离是i+1子树的数目

f1-状态压缩+动态规划

基本分析

  1. 城市个数不超过15可以暗示什么?用二进制表示当前城市的选择状态
  2. 怎么定义dp状态?dp[j]表示城市选择是j时候对应的最大距离
  3. 以上状态j一定都是合法的吗,怎么判断?j可能本身不能联通,(1)在利用j进行搭建的时候就判断一下,如果f[j]是0,表示不能搭建;(2)对需要枚举新增的节点i,看j+(1<<i)是不是能联通?做法是枚举j中的节点k,看是不是存在d[k][i]=1的点
  4. 怎么考虑转移?如果nj是联通的,这个时候就遍历j内的所有点,找出和i点距离最大的更新f[nj]
  5. 怎么取得最后的结果?f[j]的含子树是j时候的最大距离,那么遍历j,只要f[j]不等于0(对应单个点或者不成立的子树),f[j]-1的值就是ans的索引,累加ans[f[j]-1]就行

代码

class Solution:
    def countSubgraphsForEachDiameter(self, n: int, edges: List[List[int]]) -> List[int]:
        # 定义距离d
        d = [[n+1]* n for _ in range(n)]
        # 定义dp数组f
        mask = 1<<n
        f = [0] * mask

        # 相同点的d值设置为0
        for i in range(n):
            for j in range(n):
                if i == j:
                    d[i][j] = 0
        
        # 遍历相邻点,初始化d和一部分子树
        for x, y in edges:
            d[x-1][y-1] = 1
            d[y-1][x-1] = 1
            f[(1<<(x-1)) + (1<<(y-1))] = 1
        
        # Floyed初始化之间的最小距离
        for k in range(n):
            for i in range(n):
                for j in range(n):
                    if d[i][k] != n+1 and d[k][j]!= n+1:
                        d[i][j] = min(d[i][j], d[i][k]+d[k][j])

        # 遍历状态j
        for j in range(1, mask):
            # 去掉不是树的状态,通过f判断
            if f[j] == 0:
                continue
            # 遍历可能能新增的节点i,对i的要求是i不在j中,且新状态没有被计算过
            for i in range(n):
                nj = j + (1<<i)
                if j & (1<<i) != 0 or f[nj] != 0:
                    continue
                # 遍历j,看是不是能连起来
                for k in range(n):
                    if j & (1<<k) != 0 and d[k][i] ==1:
                        f[nj] = f[j]
                        break
                # 如果新状态不能存在,不考虑i了
                if f[nj] == 0:
                    continue
                # 再次遍历j中的节点,来维护dp距离
                for k in range(n):
                    if j & (1<<k) != 0:
                        f[nj] = max(f[nj], d[k][i])
        # 统计结果
        ans = [0] * (n-1)
        for j in range(1, mask):
            if f[j] != 0:
                ans[f[j]-1] += 1
        
        return ans

复杂度

时间:\(Floyed常规是O(n^3),这里由于是树为O(n^2), 枚举的过程是O(2^n*n*n)\)
空间:\(存距离矩阵是O(n^2), 存dp是O(2^n)\)

总结

  1. 几个边界需要注意:
  • (1)节点自身间的距离是
  • (2)相邻节点间的距离是1
  • (3)相邻节点构成了最小子树,对应的f值是1
  • (4)孤立的节点和不能成子树的节点组合对应的f值都是0
  • (5)判断一个组合j是不是有效,就是(1)先看对应的f值是不是0;(2)对可能的新组合nj,如果找不到k可以给nj赋值,那么nj还是0,再转移前就会退出当前对i的讨论
  1. 预处理的时候,用Floyed获取任意点间的最小距离
  2. 枚举状态的时候需要考虑:
  • (1)当前j可能是不合法的,这个时候就换下一个j,或者是合法的往下走;
  • (2)枚举需要扩展的节点i,对i的要求是<1>i不能被j包含+<2>j+i节点之前没有算过,如果满足以上要求,这个i才去判断是不是可以连接;
  • (3)怎么判断连接?j中存在一个点k和i的距离是1,如果没有的话怎么办?说明j+(1<<i)这个子树不成立,去找一下i;
  • (4)怎么更新状态,再次遍历j中的点k,每次取f[nj]和d[i][k]的最大值
  1. 求结果的时候就是遍历f,生成答案
posted @ 2022-10-24 20:03  zhangk1988  阅读(76)  评论(0)    收藏  举报