【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离

本文主要是介绍【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者推荐

【动态规划】【字符串】【行程码】1531. 压缩字符串

本文涉及的知识点

图论 深度优先搜索 状态压缩 树

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

给你 n 个城市,编号为从 1 到 n 。同时给你一个大小为 n-1 的数组 edges ,其中 edges[i] = [ui, vi] 表示城市 ui 和 vi 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵 树 。
一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。
对于 d 从 1 到 n-1 ,请你找到城市间 最大距离 恰好为 d 的所有子树数目。
请你返回一个大小为 n-1 的数组,其中第 d 个元素(下标从 1 开始)是城市间 最大距离 恰好等于 d 的子树数目。
请注意,两个城市间距离定义为它们之间需要经过的边的数目。
示例 1:
输入:n = 4, edges = [[1,2],[2,3],[2,4]]
输出:[3,4,0]
解释:
子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。
子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。
不存在城市间最大距离为 3 的子树。
示例 2:
输入:n = 2, edges = [[1,2]]
输出:[1]
示例 3:
输入:n = 3, edges = [[1,2],[2,3]]
输出:[2,1]
提示:
2 <= n <= 15
edges.length == n-1
edges[i].length == 2
1 <= ui, vi <= n
题目保证 (ui, vi) 所表示的边互不相同。

深度优先搜索

编号从1到n,变成0到n-1。
枚举所有可能的子树mask,如果mask & (1 << i ) 表示第i个节点在此树中。
任意节点为根都可以,比如:0。

状态表示

m_vDis1[mask]记录 子树mask 以根节点为起点的最长路径经过的节点数。
m_vDis2[mask]记录子树mask 最长路径 经过的节点数。
非法子树两者都为0。

子树的的转移方程

子树的某合法状态:以它为根节点的子树。
枚举各子树的状态,处理独子树。独子树为mask,根节点为root,则当前树为:mask1 = mask | ( 1 << root)。
m_vDis1[mask1] = m_vDis1[mask]+1
m_vDis2[mask1] = max(m_vDis1[mask1] ,m_vDis2[mask])
处理非独子树
vLeft 记录根节点和前i棵子树 组成 的 所有合法字子树,如:mask1,第i棵子树的某合法状态 mask2。
两者合并后的新状态为mask3 = mask1 | mask2。
m_vDis1[mask3] = max(m_vDis1[mask1] ,m_vDis1[mask2]+1)
m_vDis2[mask3] = max(m_vDis2[mask1],m_vDis2[mask2],m_vDis1[mask1]+m_vDis1[mask2])

代码

核心代码

class CNeiBo2
{
public:CNeiBo2(int n, bool bDirect, int iBase = 0) :m_iN(n), m_bDirect(bDirect), m_iBase(iBase){m_vNeiB.resize(n);}CNeiBo2(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0) :m_iN(n), m_bDirect(bDirect), m_iBase(iBase){m_vNeiB.resize(n);for (const auto& v : edges){m_vNeiB[v[0] - iBase].emplace_back(v[1] - iBase);if (!bDirect){m_vNeiB[v[1] - iBase].emplace_back(v[0] - iBase);}}}inline void Add(int iNode1, int iNode2){iNode1 -= m_iBase;iNode2 -= m_iBase;m_vNeiB[iNode1].emplace_back(iNode2);if (!m_bDirect){m_vNeiB[iNode2].emplace_back(iNode1);}}const int m_iN;const bool m_bDirect;const int m_iBase;vector<vector<int>> m_vNeiB;
};class Solution {
public:vector<int> countSubgraphsForEachDiameter(int n, vector<vector<int>>& edges) {m_vDis1.resize((1 << n));m_vDis2.resize((1 << n));CNeiBo2 neibo(n, edges, false, 1);DFS(neibo.m_vNeiB, 0, -1);vector<int> vRet(n-1);for (int i = 0; i < (1 << n); i++){const int inx = m_vDis2[i] - 2;if (inx >= 0){vRet[inx]++;}}return vRet;}vector<int> DFS(vector<vector<int>>& neiBo,int cur,int par){vector<int> statu;const int curMask = 1 << cur;statu.emplace_back(curMask);m_vDis1[curMask] = m_vDis2[curMask] = 1;for (const auto& next : neiBo[cur]){if (next == par){continue;}auto child = DFS(neiBo, next, cur);			const int iLeftCount = statu.size();for(const auto& mask : child ){{//处理独子树const int mask1 = mask | curMask;m_vDis1[mask1] = m_vDis1[mask] + 1;m_vDis2[mask1] = max(m_vDis1[mask1], m_vDis2[mask]);statu.emplace_back(mask1);}{//非独子树for (int i = iLeftCount - 1; i >= 0; i--){const int mask1 = mask | statu[i];m_vDis1[mask1] = max(m_vDis1[statu[i]], m_vDis1[mask] + 1);m_vDis2[mask1] = max(m_vDis2[statu[i]], m_vDis2[mask]);m_vDis2[mask1] = max(m_vDis2[mask1], m_vDis1[statu[i]]+ m_vDis1[mask]);statu.emplace_back(mask1);}}}}return statu;}vector<int> m_vDis1, m_vDis2;
};

测试用例

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;vector<vector<int>> edges;{Solution sln;n = 2, edges = { {1,2} };auto res = sln.countSubgraphsForEachDiameter(n, edges);Assert(res, vector<int>{1});}{Solution sln;n = 4, edges = { {1,2},{2,3},{2,4} };auto res = sln.countSubgraphsForEachDiameter(n, edges);Assert(res, vector<int>{3, 4, 0});}{Solution sln;n = 3, edges = { {1,2},{2,3} };auto res = sln.countSubgraphsForEachDiameter(n, edges);Assert(res, vector<int>{2,1});}}

2023年2月

class Solution {
public:
vector countSubgraphsForEachDiameter(int n, vector<vector>& edges) {
m_n = n;
m_vNear.resize(n);
for (const auto& v : edges)
{
m_vNear[v[0] - 1].push_back(v[1] - 1);
m_vNear[v[1] - 1].push_back(v[0] - 1);
}
DFSForDelParent(0, -1);
BSFForNodeSort(0, -1);
m_vNodeLeveMaxDist.assign(n, vector< vector>(n + 1, vector(n + 1)));
for (auto it = m_vSortNode.rbegin(); it != m_vSortNode.rend(); ++it)
{
dfs(*it);
}
vector vRet;
for (int iMaxDis = 1; iMaxDis < n; iMaxDis++)
{
int iSum = 0;
for (int iNode = 0; iNode < n; iNode++)
{
for (int leve = 0; leve <= n; leve++)
{
iSum += m_vNodeLeveMaxDist[iNode][leve][iMaxDis + 1];
}
}
vRet.push_back(iSum);
}
return vRet;
}
void DFSForDelParent(int iCur, const int iParent)
{
auto& v = m_vNear[iCur];
for (auto it = v.begin(); it != v.end(); ++ it )
{
if (iParent == *it )
{
v.erase(it);
break;
}
}
for (auto it = v.begin(); it != v.end(); ++it)
{
DFSForDelParent(*it, iCur);
}
}
void BSFForNodeSort(int iCur, const int iParent)
{
m_vSortNode.push_back(iCur);
int iNotSubNode = m_vSortNode.size();
for (const auto& next : m_vNear[iCur])
{
m_vSortNode.push_back(next);
}
const int iNodeNum = m_vSortNode.size();
for (int i = iNotSubNode; i < iNodeNum; i++)
{
BSFForNodeSort(m_vSortNode[i], iCur);
}
}
void dfs(int iCur)
{
vector<vector> pre(m_n + 1, vector(m_n + 1));
pre[0+1][-1+1] = 1;
for (const auto& next : m_vNear[iCur])
{
vector<vector> dp(m_n + 1, vector(m_n + 1));
for (int iPreLeve = -1; iPreLeve < m_n; iPreLeve++)
{
for (int iPreMaxDis = -1; iPreMaxDis < m_n; iPreMaxDis++)
{
if (0 == pre[iPreLeve + 1][iPreMaxDis + 1])
{
continue;
}
for (int iLeve = -1; iLeve < m_n; iLeve++)
{
for (int iMaxDis = -1; iMaxDis < m_n; iMaxDis++)
{
if (0 == m_vNodeLeveMaxDist[next][iLeve + 1][iMaxDis + 1])
{
continue;
}
int iNewMaxDis = max(iPreMaxDis, max(iMaxDis, iLeve));
iNewMaxDis = max(iNewMaxDis, iPreLeve + iLeve+1 );
int iNewLeve = max(iPreLeve, iLeve + 1);
dp[iNewLeve + 1][iNewMaxDis + 1] += pre[iPreLeve + 1][iPreMaxDis + 1] * m_vNodeLeveMaxDist[next][iLeve + 1][iMaxDis + 1];
}
}
}
}
pre.swap(dp);
}
pre[-1 + 1][-1 + 1]++;
m_vNodeLeveMaxDist[iCur] = pre;
}
vector m_vSortNode;//根节点,一级节点,二级节点…
vector<vector> m_vNear;
vector<vector<vector>> m_vNodeLeveMaxDist;
int m_n;
};

2023年9月

class Solution {
public:
vector countSubgraphsForEachDiameter(int n, vector<vector>& edges) {
CNeiBo2 neiBo(n, edges, false, 1);
AssignVector3(m_vLeveNums,n,n,n,0);
dfs(0, -1, neiBo);
vector vRet(n - 1);
for (int Dis = 1; Dis < n; Dis++)
{
for (int node = 0; node < n; node++)
{
for (int leve = 0; leve < n; leve++)
{
vRet[Dis - 1] += m_vLeveNums[node][leve][Dis];
}
}
}
return vRet;
}
void dfs(int cur,int parent, CNeiBo2& neiBo)
{
m_vLeveNums[cur][0][0] = 1;
for (const auto& next : neiBo.m_vNeiB[cur])
{
if (next == parent)
{
continue;
}
dfs(next, cur, neiBo);
auto pre = m_vLeveNums[cur];
for (int preLeve = 0; preLeve < pre.size(); preLeve++)
{
for (int preDis = 0; preDis < pre.size(); preDis++)
{
for (int nextLeve = 0; nextLeve + 1 < pre.size(); nextLeve++)
{
for (int nextDis = 0; nextDis < pre.size(); nextDis++)
{
const int iNewLeve = max(nextLeve + 1, preLeve);
int iNewDis = max(preDis, preLeve + nextLeve + 1);
MaxSelf(&iNewDis, nextDis);
if (iNewDis >= pre.size())
{
continue;
}
m_vLeveNums[cur][iNewLeve][iNewDis] += pre[preLeve][preDis] * m_vLeveNums[next][nextLeve][nextDis];
}
}
}
}
}
}
vector<vector<vector>> m_vLeveNums;//m_vRet[cur][i][j]表示以cur为根 ,叶子距离cur最大距离为i,任意两个节点最大距离为j。m_vRet[cur][0][0]表示只有根节点
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++**实现。

这篇关于【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/663072

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

hdu1496(用hash思想统计数目)

作为一个刚学hash的孩子,感觉这道题目很不错,灵活的运用的数组的下标。 解题步骤:如果用常规方法解,那么时间复杂度为O(n^4),肯定会超时,然后参考了网上的解题方法,将等式分成两个部分,a*x1^2+b*x2^2和c*x3^2+d*x4^2, 各自作为数组的下标,如果两部分相加为0,则满足等式; 代码如下: #include<iostream>#include<algorithm

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 3723 kruscal,反边取最大生成树。

题意: 需要征募女兵N人,男兵M人。 每征募一个人需要花费10000美元,但是如果已经招募的人中有一些关系亲密的人,那么可以少花一些钱。 给出若干的男女之间的1~9999之间的亲密关系度,征募某个人的费用是10000 - (已经征募的人中和自己的亲密度的最大值)。 要求通过适当的招募顺序使得征募所有人的费用最小。 解析: 先设想无向图,在征募某个人a时,如果使用了a和b之间的关系

poj 2431 poj 3253 优先队列的运用

poj 2431: 题意: 一条路起点为0, 终点为l。 卡车初始时在0点,并且有p升油,假设油箱无限大。 给n个加油站,每个加油站距离终点 l 距离为 x[i],可以加的油量为fuel[i]。 问最少加几次油可以到达终点,若不能到达,输出-1。 解析: 《挑战程序设计竞赛》: “在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一