【树上倍增】【割点】 【换根法】3067. 在带权树网络中统计可连接服务器对数目(一)

简介: 【树上倍增】【割点】 【换根法】3067. 在带权树网络中统计可连接服务器对数目

本文涉及知识点

树上倍增 树 图论 并集查找 换根法 深度优先

割点原理及封装好的割点类(预计2024年3月11号左右发布)

LeetCode3067. 在带权树网络中统计可连接服务器对数目

给你一棵无根带权树,树中总共有 n 个节点,分别表示 n 个服务器,服务器从 0 到 n - 1 编号。同时给你一个数组 edges ,其中 edges[i] = [ai, bi, weighti] 表示节点 ai 和 bi 之间有一条双向边,边的权值为 weighti 。再给你一个整数 signalSpeed 。

如果两个服务器 a ,b 和 c 满足以下条件,那么我们称服务器 a 和 b 是通过服务器 c 可连接的 :

a < b ,a != c 且 b != c 。

从 c 到 a 的距离是可以被 signalSpeed 整除的。

从 c 到 b 的距离是可以被 signalSpeed 整除的。

从 c 到 b 的路径与从 c 到 a 的路径没有任何公共边。

请你返回一个长度为 n 的整数数组 count ,其中 count[i] 表示通过服务器 i 可连接 的服务器对的 数目 。

示例 1:

输入:edges = [[0,1,1],[1,2,5],[2,3,13],[3,4,9],[4,5,2]], signalSpeed = 1

输出:[0,4,6,6,4,0]

解释:由于 signalSpeed 等于 1 ,count[c] 等于所有从 c 开始且没有公共边的路径对数目。

在输入图中,count[c] 等于服务器 c 左边服务器数目乘以右边服务器数目。

示例 2:

输入:edges = [[0,6,3],[6,5,3],[0,3,1],[3,2,7],[3,1,6],[3,4,2]], signalSpeed = 3

输出:[2,0,0,0,0,0,2]

解释:通过服务器 0 ,有 2 个可连接服务器对(4, 5) 和 (4, 6) 。

通过服务器 6 ,有 2 个可连接服务器对 (4, 5) 和 (0, 5) 。

所有服务器对都必须通过服务器 0 或 6 才可连接,所以其他服务器对应的可连接服务器对数目都为 0 。

提示:

2 <= n <= 1000

edges.length == n - 1

edges[i].length == 3

0 <= ai, bi < n

edges[i] = [ai, bi, weighti]

1 <= weighti <= 106

1 <= signalSpeed <= 106

输入保证 edges 构成一棵合法的树。

树上倍增

本题有三个考点:

一,如何计算树上两个节点x1,x2的距离。

假定这两个节点的最早公共祖先是pub。以任意节点root为根,f(x)表示节点x到root的距离。

x1到x2的距离:f(x1)+f(x2)-2*f(pub)。

二,如何找到最早公共祖先:树上倍增。

记录各节点的1级祖先(父节点)、2级祖先、4级祖先…

三,如果判断ac和bc有公共边。

树是连通无向无环图,因为无环,所以两个节点的路径唯一。

假设公共边是x3x4。则x3到c的路径唯一,假定x3到c的倒数第二个端点是x5,则ab和bc的最后一条边都是x 3 c → \overrightarrow{x3c}x3c。断开所以和c相连的边,如果a和b在同一个连通区域,则有公共边。用并查集看是否在同一个连通区域。

时间复杂度: O(nnlogn)。 枚举c,时间复杂度O(n);枚举ab,时间复杂度O(n)。查公共路径O(logn)。

并集查找

class CNeiBo
{
public: 
  static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0) 
  {
    vector<vector<int>>  vNeiBo(n);
    for (const auto& v : edges)
    {
      vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
      if (!bDirect)
      {
        vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
      }
    }
    return vNeiBo;
  } 
  static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
  {
    vector<vector<std::pair<int, int>>> vNeiBo(n);
    for (const auto& v : edges)
    {
      vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
      if (!bDirect)
      {
        vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
      }
    }
    return vNeiBo;
  }
};
class CUnionFind
{
public:
  CUnionFind(int iSize) :m_vNodeToRegion(iSize)
  {
    for (int i = 0; i < iSize; i++)
    {
      m_vNodeToRegion[i] = i;
    }
    m_iConnetRegionCount = iSize;
  } 
  CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size())
  {
    for (int i = 0; i < vNeiBo.size(); i++) {
      for (const auto& n : vNeiBo[i]) {
        Union(i, n);
      }
    }
  }
  int GetConnectRegionIndex(int iNode)
  {
    int& iConnectNO = m_vNodeToRegion[iNode];
    if (iNode == iConnectNO)
    {
      return iNode;
    }
    return iConnectNO = GetConnectRegionIndex(iConnectNO);
  }
  void Union(int iNode1, int iNode2)
  {
    const int iConnectNO1 = GetConnectRegionIndex(iNode1);
    const int iConnectNO2 = GetConnectRegionIndex(iNode2);
    if (iConnectNO1 == iConnectNO2)
    {
      return;
    }
    m_iConnetRegionCount--;
    if (iConnectNO1 > iConnectNO2)
    {
      UnionConnect(iConnectNO1, iConnectNO2);
    }
    else
    {
      UnionConnect(iConnectNO2, iConnectNO1);
    }
  }
  bool IsConnect(int iNode1, int iNode2)
  {
    return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);
  }
  int GetConnetRegionCount()const
  {
    return m_iConnetRegionCount;
  }
  vector<int> GetNodeCountOfRegion()//各联通区域的节点数量
  {
    const int iNodeSize = m_vNodeToRegion.size();
    vector<int> vRet(iNodeSize);
    for (int i = 0; i < iNodeSize; i++)
    {
      vRet[GetConnectRegionIndex(i)]++;
    }
    return vRet;
  }
  std::unordered_map<int, vector<int>> GetNodeOfRegion()
  {
    std::unordered_map<int, vector<int>> ret;
    const int iNodeSize = m_vNodeToRegion.size();
    for (int i = 0; i < iNodeSize; i++)
    {
      ret[GetConnectRegionIndex(i)].emplace_back(i);
    }
    return ret;
  }
private:
  void UnionConnect(int iFrom, int iTo)
  {
    m_vNodeToRegion[iFrom] = iTo;
  }
  vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引
  int m_iConnetRegionCount;
};
class CParents
{
public:
  CParents(vector<int>& vParent, const vector<int>& vLeve):m_vLeve(vLeve)
  {
    const int iMaxLeve = *std::max_element(vLeve.begin(), vLeve.end());
    int iBitNum = 0;
    for (; (1 << iBitNum) < iMaxLeve; iBitNum++);
    const int n = vParent.size();
    m_vParents.assign(iBitNum+1, vector<int>(n, -1));
    m_vParents[0] = vParent;
    //树上倍增
    for (int i = 1; i < m_vParents.size(); i++)
    {
      for (int j = 0; j < n; j++)
      {
        const int iPre = m_vParents[i - 1][j];
        if (-1 != iPre)
        {
          m_vParents[i][j] = m_vParents[i - 1][iPre];
        }
      }
    }
  }
  int GetParent(int iNode, int iLeve)const
  {
    int iParent = iNode;
    for (int iBit = 0; iBit < m_vParents.size(); iBit++)
    {
      if (-1 == iParent)
      {
        return iParent;
      }
      if (iLeve & (1 << iBit))
      {
        iParent = m_vParents[iBit][iParent];
      }
    }
    return iParent;
  }
  int GetPublicParent(int iNode1, int iNode2)const
  {
    int leve0 = m_vLeve[iNode1];
    int leve1 = m_vLeve[iNode2];
    if (leve0 < leve1)
    {
      iNode2 = GetParent(iNode2, leve1 - leve0);
      leve1 = leve0;
    }
    else
    {
      iNode1 = GetParent(iNode1, leve0 - leve1);
      leve0 = leve1;
    }
    //二分查找
    int left = -1, r = leve0;
    while (r - left > 1)
    {
      const auto mid = left + (r - left) / 2;
      const int iParent0 = GetParent(iNode1, mid);
      const int iParent1 = GetParent(iNode2, mid);
      if (iParent0 == iParent1)
      {
        r = mid;
      }
      else
      {
        left = mid;
      }
    }
    return GetParent(iNode1, r);
  }
protected:
  vector<vector<int>> m_vParents;
  const vector<int> m_vLeve;
};
class Solution {
public:
  vector<int> countPairsOfConnectableServers(vector<vector<int>>& edges, int signalSpeed) {
    m_c = edges.size() + 1;
    m_vDisToRoot.resize(m_c);
    m_vParent.resize(m_c);
    m_vLeve.resize(m_c);
    auto neiBo = CNeiBo::Three(m_c, edges, false, 0);
    DFS(neiBo, 0, -1, 0,0); 
    CParents par(m_vParent,m_vLeve);
    vector<int> vRet(m_c);
    for (int c = 0; c < m_c; c++)
    {
      CUnionFind uf(m_c);
      for (const auto& v : edges)
      {
        if ((v[0] == c) || (v[1] == c))
        {
          continue;
        }
        uf.Union(v[0], v[1]);
      }
      vector<int> vRegionCnt(m_c);
      for (int ab = 0; ab < m_c; ab++)
      {
        if (ab == c )
        {
          continue;
        }
        const int pub = par.GetPublicParent(ab, c);
        const int len = m_vDisToRoot[ab] + m_vDisToRoot[c] - 2 * m_vDisToRoot[pub];
        if (0 != len % signalSpeed)
        {
          continue;
        }
        vRegionCnt[uf.GetConnectRegionIndex(ab)]++;
      }
      int&iRet = vRet[c];
      const int total = std::accumulate(vRegionCnt.begin(), vRegionCnt.end(), 0);
      for (int c1 = 0; c1 < m_c; c1++)
      {
        iRet += vRegionCnt[c1] * (total - vRegionCnt[c1]);
      } 
      iRet /= 2;
    }
    return vRet;
  }
  void DFS(vector<vector<std::pair<int, int>>>& neiBo, int cur, int par,int leve,int dis)
  {
    m_vDisToRoot[cur] =dis;
    m_vParent[cur] = par;
    m_vLeve[cur] = leve;
    for (const auto& [next,len] : neiBo[cur])
    {
      if (next == par)
      {
        continue;
      }
      DFS(neiBo, next, cur,leve+1,dis+len);
    }
  }
  vector<int> m_vDisToRoot,m_vParent,m_vLeve;
  int m_c;
};

测试用例

template<class T,class T2>
void Assert(const T& t1, const T2& 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()
{
  vector<vector<int>> edges;
  int signalSpeed;
  {
    Solution sln;
    edges = {  }, signalSpeed = 1;
    auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
    Assert({ 0 }, res);
  }
  {
    Solution sln;
    edges = { {0,1,1} }, signalSpeed = 1;
    auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
    Assert({ 0,0 }, res);
  }
  {
    Solution sln;
    edges = { {0,1,1},{1,2,1} }, signalSpeed = 1;
    auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
    Assert({ 0,1,0 }, res);
  }
  {
    Solution sln;
    edges = { {0,1,1},{1,2,5},{2,3,13},{3,4,9},{4,5,2} }, signalSpeed = 1;
    auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
    Assert({ 0,4,6,6,4,0 } , res);
  }
  {
    Solution sln;
    edges = { {0,6,3},{6,5,3},{0,3,1},{3,2,7},{3,1,6},{3,4,2} }, signalSpeed = 3;
    auto res = sln.countPairsOfConnectableServers(edges, signalSpeed);
    Assert({ 2,0,0,0,0,0,2 }, res);
  }
}


【树上倍增】【割点】 【换根法】3067. 在带权树网络中统计可连接服务器对数目(二)https://developer.aliyun.com/article/1478724

相关文章
|
2月前
|
机器学习/深度学习 人工智能 运维
企业内训|LLM大模型在服务器和IT网络运维中的应用-某日企IT运维部门
本课程是为某在华日资企业集团的IT运维部门专门定制开发的企业培训课程,本课程旨在深入探讨大型语言模型(LLM)在服务器及IT网络运维中的应用,结合当前技术趋势与行业需求,帮助学员掌握LLM如何为运维工作赋能。通过系统的理论讲解与实践操作,学员将了解LLM的基本知识、模型架构及其在实际运维场景中的应用,如日志分析、故障诊断、网络安全与性能优化等。
92 2
|
4天前
|
负载均衡 网络协议 算法
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
这网络层就像搭积木一样,上层协议都是基于下层协议搭出来的。不管是ping(用了ICMP协议)还是tcp本质上都是基于网络层IP协议的数据包,而到了物理层,都是二进制01串,都走网卡发出去了。 如果网络环境没发生变化,目的地又一样,那按道理说他们走的网络路径应该是一样的,什么情况下会不同呢? 我们就从路由这个话题聊起吧。
20 4
不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?
|
13天前
|
机器学习/深度学习 数据采集 人工智能
基于Huffman树的层次化Softmax:面向大规模神经网络的高效概率计算方法
层次化Softmax算法通过引入Huffman树结构,将传统Softmax的计算复杂度从线性降至对数级别,显著提升了大规模词汇表的训练效率。该算法不仅优化了计算效率,还在处理大规模离散分布问题上提供了新的思路。文章详细介绍了Huffman树的构建、节点编码、概率计算及基于Gensim的实现方法,并讨论了工程实现中的优化策略与应用实践。
61 15
基于Huffman树的层次化Softmax:面向大规模神经网络的高效概率计算方法
|
3天前
|
缓存 负载均衡 监控
HTTP代理服务器在网络安全中的重要性
随着科技和互联网的发展,HTTP代理IP中的代理服务器在企业业务中扮演重要角色。其主要作用包括:保护用户信息、访问控制、缓存内容、负载均衡、日志记录和协议转换,从而在网络管理、性能优化和安全性方面发挥关键作用。
18 2
|
1月前
|
网络虚拟化
生成树协议(STP)及其演进版本RSTP和MSTP,旨在解决网络中的环路问题,提高网络的可靠性和稳定性
生成树协议(STP)及其演进版本RSTP和MSTP,旨在解决网络中的环路问题,提高网络的可靠性和稳定性。本文介绍了这三种协议的原理、特点及区别,并提供了思科和华为设备的命令示例,帮助读者更好地理解和应用这些协议。
54 4
|
2月前
|
存储 安全 数据可视化
提升网络安全防御有效性,服务器DDoS防御软件解读
提升网络安全防御有效性,服务器DDoS防御软件解读
56 1
提升网络安全防御有效性,服务器DDoS防御软件解读
|
1月前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
625 2
|
1月前
|
物联网 5G 数据中心
|
2月前
|
Docker 容器
docker swarm启动服务并连接到网络
【10月更文挑战第16天】
45 5
|
2月前
|
安全 网络架构
无线网络:连接未来的无形纽带
【10月更文挑战第13天】
77 8