【图论 状态压缩 枚举】2959. 关闭分部的可行集合数目

简介: 【图论 状态压缩 枚举】2959. 关闭分部的可行集合数目

本文涉及知识点

图论 状态压缩 枚举 多源路径

LeetCode2959. 关闭分部的可行集合数目

一个公司在全国有 n 个分部,它们之间有的有道路连接。一开始,所有分部通过这些道路两两之间互相可以到达。

公司意识到在分部之间旅行花费了太多时间,所以它们决定关闭一些分部(也可能不关闭任何分部),同时保证剩下的分部之间两两互相可以到达且最远距离不超过 maxDistance 。

两个分部之间的 距离 是通过道路长度之和的 最小值

给你整数 n ,maxDistance 和下标从 0 开始的二维整数数组 roads ,其中 roads[i] = [ui, vi, wi] 表示一条从 ui 到 vi 长度为 wi的 无向 道路。

请你返回关闭分部的可行方案数目,满足每个方案里剩余分部之间的最远距离不超过 maxDistance。

注意,关闭一个分部后,与之相连的所有道路不可通行。

注意,两个分部之间可能会有多条道路。

示例 1:

输入:n = 3, maxDistance = 5, roads = [[0,1,2],[1,2,10],[0,2,10]]

输出:5

解释:可行的关闭分部方案有:

  • 关闭分部集合 [2] ,剩余分部为 [0,1] ,它们之间的距离为 2 。
  • 关闭分部集合 [0,1] ,剩余分部为 [2] 。
  • 关闭分部集合 [1,2] ,剩余分部为 [0] 。
  • 关闭分部集合 [0,2] ,剩余分部为 [1] 。
  • 关闭分部集合 [0,1,2] ,关闭后没有剩余分部。
    总共有 5 种可行的关闭方案。
    示例 2:
    输入:n = 3, maxDistance = 5, roads = [[0,1,20],[0,1,10],[1,2,2],[0,2,2]]
    输出:7
    解释:可行的关闭分部方案有:
  • 关闭分部集合 [] ,剩余分部为 [0,1,2] ,它们之间的最远距离为 4 。
  • 关闭分部集合 [0] ,剩余分部为 [1,2] ,它们之间的距离为 2 。
  • 关闭分部集合 [1] ,剩余分部为 [0,2] ,它们之间的距离为 2 。
  • 关闭分部集合 [0,1] ,剩余分部为 [2] 。
  • 关闭分部集合 [1,2] ,剩余分部为 [0] 。
  • 关闭分部集合 [0,2] ,剩余分部为 [1] 。
  • 关闭分部集合 [0,1,2] ,关闭后没有剩余分部。
    总共有 7 种可行的关闭方案。
    示例 3:
    输入:n = 1, maxDistance = 10, roads = []
    输出:2
    解释:可行的关闭分部方案有:
  • 关闭分部集合 [] ,剩余分部为 [0] 。
  • 关闭分部集合 [0] ,关闭后没有剩余分部。
    总共有 2 种可行的关闭方案。

提示:

1 <= n <= 10

1 <= maxDistance <= 105

0 <= roads.length <= 1000

roads[i].length == 3

0 <= ui, vi <= n - 1

ui != vi

1 <= wi <= 1000

一开始所有分部之间通过道路互相可以到达。

多源路径

枚举所有没关闭的分部,共2n种可能。

每种可能最多10个节点,利用Flod 求多源路径,时间复杂度O(nnn)。

故总时间复杂度O(2nnnn)。

代码

//多源码路径
template<class T, T INF = 1000 * 1000 * 1000>
class CFloyd
{
public:
  CFloyd(const  vector<vector<T>>& mat)
  {
    m_vMat = mat;
    const int n = mat.size();
    for (int i = 0; i < n; i++)
    {//通过i中转
      for (int i1 = 0; i1 < n; i1++)
      {
        if (INF == m_vMat[i1][i])
        {
          continue;
        }
        for (int i2 = 0; i2 < n; i2++)
        {
          //此时:m_vMat[i1][i2] 表示通过[0,i)中转的最短距离
          m_vMat[i1][i2] = min(m_vMat[i1][i2], m_vMat[i1][i] + m_vMat[i][i2]);
          //m_vMat[i1][i2] 表示通过[0,i]中转的最短距离
        }
      }
    }
  };
  vector<vector<T>> m_vMat;
};
class Solution {
public:
  int numberOfSets(int n, int maxDistance, vector<vector<int>>& roads) {
    int iRet = 0;
    for (int iMask = 0; iMask < (1 << n); iMask++)
    {
      iRet += Do(iMask, n, maxDistance, roads);
    }
    return iRet;
  }
  bool Do(int iMask, int n, int maxDistance, vector<vector<int>>& roads)
  {
    vector<vector<int>> mat(n, vector<int>(n, 1000'000'000));
    for (int i = 0; i < n; i++) {
      mat[i][i] = 0;
    }
    for (const auto& v : roads)
    {
      if ((iMask & (1 << v[0])) && (iMask & (1 << v[1]))) {
        mat[v[1]][v[0]] = mat[v[0]][v[1]] = min(mat[v[0]][v[1]], v[2]); 
      }
    }
    CFloyd<int> floyd(mat);
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++)
      {
        if ((iMask & (1 << i)) && (iMask & (1 << j)))
        {
          if (floyd.m_vMat[i][j] > maxDistance)
          {
            return false;
          }
        }
      }
    }
    return true;
  }
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  int n, maxDistance;
  vector<vector<int>> roads;
  {
    n = 3, maxDistance = 12, roads = { {1, 0, 11},{1, 0, 16},{0, 2, 13} };
    auto res = Solution().numberOfSets(n, maxDistance, roads);
    Assert(5, res);
  }
  {
    n = 3, maxDistance = 5, roads = { {0,1,2},{1,2,10},{0,2,10} };
    auto res = Solution().numberOfSets(n, maxDistance, roads);
    Assert(5 , res);
  }
  
  
  //CConsole::Out(res);
}

优化了封装类

template<class T = int >
class CFloyd
{
public:
  CFloyd(int n, const T INF = 1000 * 1000 * 1000):m_INF(INF)
  {
    m_vMat.assign(n, vector<T>(n, m_INF));
    for (int i = 0; i < n; i++) {
      m_vMat[i][i] = 0;
    }
  }
  void SetEdge(int i1, int i2,const T& dis, bool bDirect = false)
  {
    m_vMat[i1][i2] = min(m_vMat[i1][i2],dis);
    if (!bDirect) {
      m_vMat[i2][i1] = m_vMat[i1][i2];
    }
  }
  vector<vector<T>> Dis()
  {
    auto vResMat = m_vMat;
    const int n = m_vMat.size();
    for (int i = 0; i < n; i++)
    {//通过i中转
      for (int i1 = 0; i1 < n; i1++)
      {
        if (m_INF == vResMat[i1][i])
        {
          continue;
        }
        for (int i2 = 0; i2 < n; i2++)
        {
          //此时:m_vMat[i1][i2] 表示通过[0,i)中转的最短距离
          vResMat[i1][i2] = min(vResMat[i1][i2], vResMat[i1][i] + vResMat[i][i2]);
          //m_vMat[i1][i2] 表示通过[0,i]中转的最短距离
        }
      }
    }
    return vResMat;
  };
  vector<vector<T>> m_vMat;//结果串
  const T m_INF;
};
class Solution {
public:
  int numberOfSets(int n, int maxDistance, vector<vector<int>>& roads) {
    int iRet = 0;
    for (int iMask = 0; iMask < (1 << n); iMask++)
    {
      iRet += Do(iMask, n, maxDistance, roads);
    }
    return iRet;
  }
  bool Do(int iMask, int n, int maxDistance, vector<vector<int>>& roads)
  {     
    CFloyd<int> floyd(n); 
    for (const auto& v : roads)
    {
      if ((iMask & (1 << v[0])) && (iMask & (1 << v[1]))) {
        floyd.SetEdge(v[0],v[1],v[2]);
      }
    }
    auto res = floyd.Dis();
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++)
      {
        if ((iMask & (1 << i)) && (iMask & (1 << j)))
        {
          if (res[i][j] > maxDistance)
          {
            return false;
          }
        }
      }
    }
    return true;
  }
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关文章
|
6月前
|
算法 测试技术 C#
【动态规划】【同余前缀和】【多重背包】[推荐]2902. 和带限制的子多重集合的数目
【动态规划】【同余前缀和】【多重背包】[推荐]2902. 和带限制的子多重集合的数目
|
6月前
|
算法 测试技术 C++
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
|
6月前
|
算法 测试技术 C#
【并集查找 图论 位运算】3108. 带权图里旅途的最小代价
【并集查找 图论 位运算】3108. 带权图里旅途的最小代价
定义变量的方法,元素周期表数据集,以元素的原子序数和电子结构为基础进行排列的
定义变量的方法,元素周期表数据集,以元素的原子序数和电子结构为基础进行排列的
|
6月前
|
算法 测试技术 C#
【状态机dp 状态压缩 分组】1994. 好子集的数目
【状态机dp 状态压缩 分组】1994. 好子集的数目
|
6月前
|
算法 测试技术 C#
【动态规划】【数论】【区间合并】3041. 修改数组后最大化数组中的连续元素数目
【动态规划】【数论】【区间合并】3041. 修改数组后最大化数组中的连续元素数目
|
算法 安全 机器人
算法提高:计算几何基础 | 判断包含关系
计算几何是计算机科学的一个重要分支,主要研究几何形体的数学描述和计算机描述,在现代工程和数学领域,以及计算机辅助设计、地理信息系统、图形学、机器人技术、超大规模集成电路设计和统计等诸多领域都有重要的用途。在 ACM 竞赛中,出题相对独立,曾出现过与图论、动态规划相结合的题,大多数计算几何问题用程序实现都比较复杂。常用算法包括经典的凸包求解、离散化及扫描线算法、旋转卡壳、半平面交等。本文介绍计算几何常用算法——包含关系。
159 0
【集合论】序关系 ( 偏序集元素之间的关系 | 可比 | 严格小于 | 覆盖 | 哈斯图 )
【集合论】序关系 ( 偏序集元素之间的关系 | 可比 | 严格小于 | 覆盖 | 哈斯图 )
427 0
【集合论】序关系 ( 偏序集元素之间的关系 | 可比 | 严格小于 | 覆盖 | 哈斯图 )
(枚举)(模拟)(前缀和)(数组模拟哈希)(可二分)1236. 递增三元组
(枚举)(模拟)(前缀和)(数组模拟哈希)(可二分)1236. 递增三元组
67 0
|
存储
【每日一题Day90】LC1814统计一个数组中好对子的数目 | 哈希表
思路:如果两个数满足好对子,那么这两个数反转后的变化量相同。因此可以使用哈希表存放反转后的变化量及其次数count,该变化量存在的所有好对子个数为count∗(count−1)/2
72 0