【图论】【状态压缩】【树】【深度优先搜索】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

相关文章

Python在二进制文件中进行数据搜索的实战指南

《Python在二进制文件中进行数据搜索的实战指南》在二进制文件中搜索特定数据是编程中常见的任务,尤其在日志分析、程序调试和二进制数据处理中尤为重要,下面我们就来看看如何使用Python实现这一功能吧... 目录简介1. 二进制文件搜索概述2. python二进制模式文件读取(rb)2.1 二进制模式与文本

C++ 右值引用(rvalue references)与移动语义(move semantics)深度解析

《C++右值引用(rvaluereferences)与移动语义(movesemantics)深度解析》文章主要介绍了C++右值引用和移动语义的设计动机、基本概念、实现方式以及在实际编程中的应用,... 目录一、右值引用(rvalue references)与移动语义(move semantics)设计动机1

Prometheus+cpolar如何在手机上也能监控服务器状态?

《Prometheus+cpolar如何在手机上也能监控服务器状态?》本文强调了通过Cpolar这一内网穿透工具,轻松突破Prometheus仅限于局域网访问的限制,实现外网随时随地访问监控数据,教你... 目录前言1.安装prometheus2.安装cpolar实现随时随地开发3.配置公网地址4.保留固定

Spring Boot基于 JWT 优化 Spring Security 无状态登录实战指南

《SpringBoot基于JWT优化SpringSecurity无状态登录实战指南》本文介绍如何使用JWT优化SpringSecurity实现无状态登录,提高接口安全性,并通过实际操作步骤... 目录Spring Boot 实战:基于 JWT 优化 Spring Security 无状态登录一、先搞懂:为什

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

Java枚举类型深度详解

《Java枚举类型深度详解》Java的枚举类型(enum)是一种强大的工具,它不仅可以让你的代码更简洁、可读,而且通过类型安全、常量集合、方法重写和接口实现等特性,使得枚举在很多场景下都非常有用,本文... 目录前言1. enum关键字的使用:定义枚举类型什么是枚举类型?如何定义枚举类型?使用枚举类型:2.

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4