2023-03-20:给定一个无向图,保证所有节点连成一棵树,没有环,
给定一个正数n为节点数,所以节点编号为0~n-1,那么就一定有n-1条边,
每条边形式为{a, b, w},意思是a和b之间的无向边,权值为w,
要求:给定一个正数k,表示在挑选之后,每个点相连的边,数量都不能超过k,
注意:是每个点的连接数量,都不超过k!不是总连接数量不能超过k!
你可以随意挑选边留下,剩下的边删掉,但是要满足上面的要求。
返回不违反要求的情况下,你挑选边所能达到的最大权值累加和。
来自Lucid Air。

答案2023-03-20:

1.算法分析

为了解决此问题,我们可以使用搜索和动态规划技术进行优化,下面将详细介绍两种算法的实现方法。

1.1.暴力搜索

首先,我们可以用暴力搜索来解决这个问题。具体地,我们从第一条边开始遍历,对于每条边,有两种选择:选择它或不选择它。如果选择当前边,则需要检查所有与该边相邻的点的度数是否小于等于k;如果不是,则说明该方案不符合条件,需要跳过。否则,递归考虑下一条边。最终,我们得到所有符合要求的方案,计算它们的总权值,取其中最大值即可。

虽然暴力搜索很容易理解,但时间复杂度为 O(2^n),显然无法处理大规模数据。

下面是 Rust 实现的代码:

// 暴力方法
// 为了验证
fn max_sum1(n: i32, k: i32, edges: &Vec<Vec<i32>>) -> i32 {
process(edges, 0, &mut vec![false; edges.len()], n, k)
} // 递归函数,尝试选择或不选择边,返回最大权值和
fn process(edges: &Vec<Vec<i32>>, i: usize, pick: &mut Vec<bool>, n: i32, k: i32) -> i32 {
// 如果已经枚举完所有的边
if i == edges.len() {
// 统计每个点的度数,并计算当前选中边的权值和
let mut cnt = vec![0; n as usize];
let mut ans = 0;
for j in 0..edges.len() {
if pick[j] {
cnt[edges[j][0] as usize] += 1;
cnt[edges[j][1] as usize] += 1;
ans += edges[j][2];
}
}
// 判断每个点的度数是否小于等于k,如果不是则返回-1
for j in 0..n as usize {
if cnt[j] > k {
return -1;
}
}
// 返回当前选中边的权值和
return ans;
} else {
// 尝试选择当前边
pick[i] = true;
let p1 = process(edges, i + 1, pick, n, k);
// 不选择当前边
pick[i] = false;
let p2 = process(edges, i + 1, pick, n, k);
// 返回两种情况的最大值
return p1.max(p2);
}
}

1.2.优化的深度优先搜索

为了提高效率,我们需要对暴力搜索进行优化。我们可以利用记忆化搜索来避免重复计算,并将问题拆分为两个状态,分别表示当前节点选择和不选择的最大权值和。具体地,我们从叶子节点开始向上递推,并维护一个辅助数组,记录与当前节点相邻的子节点选择当前节点时,与不选择当前节点时的权值差。然后,根据这个数组,对DP数组中的两个状态进行更新。最后,返回根节点的“不选择”状态即可。

下面是具体的实现步骤:

(1)首先,定义两个全局变量 DP 和 HELP。DP[i][0] 表示不选择第 i 个节点时的最大权值和,DP[i][1] 表示选择第 i 个节点时的最大权值和。HELP 数组用于辅助计算,记录与当前节点相邻的子节点选择当前节点时,与不选择当前节点时的权值差。

(2)接下来,我们构造邻接表来表示输入的树。对于每个节点,我们存储一个包含其相邻节点的列表,同时也存储每条边的权值。例如,对于边 (i, j) 来说,我们将 (j,c) 添加到第 i 个节点的相邻节点列表中,将 (i,c) 添加到第 j 个节点的相邻节点列表中,其中 c 表示边的权值。

(3)然后,我们调用 dfs 函数,从根节点开始遍历整棵树。dfs 函数接受一个参数 i,表示当前节点的编号,以及一个参数 parent,表示当前节点的父节点。初始时,我们将 DP[i][1] 初始化为该节点与其相邻节点的权值之和,DP[i][0] 初始化为 0。

(4)接下来,我们遍历当前节点的相邻节点 j,并判断当前节点是否为其父节点。如果是,则跳过;否则,递归调用 dfs 函数处理子节点 j。

(5)在处理完子节点 j 后,我们需要更新 DP 和 HELP 数组。具体地,我们定义变量 sum 表示当前节点选择时的最大权值和,diff 表示当前节点不选择时的最大权值和。然后,我们遍历当前节点的相邻节点,并累加它们相对于当前节点选中或不选中的权值差到 HELP 数组中。最后,根据 HELP 数组,我们更新当前节点的 DP 值。更新方式如下:

如果当前节点选择,则有 DP[i][1] = sum + HELP[j][1],DP[i][0] = max(DP[i][0], sum + HELP[j][0]);
如果当前节点不选择,则有 DP[i][0] = diff + HELP[j][1],DP[i][1] = max(DP[i][1], diff + HELP[j][0])。
注意,在更新 DP[i][1] 时,我们需要加上当前节点与子节点 j 之间的边的权值。最后,我们返回 DP[root][0] 即可得到答案。

(6)最后,我们可以在 main 函数中读入输入数据,调用 dfs 函数求解问题,并输出结果。

使用优化的深度优先搜索算法,时间复杂度为 O(n),空间复杂度为 O(n)。

下面是 Rust 实现的代码:

// 定义两个全局变量
static mut DP: [[i32; 2]; 100001] = [[0; 2]; 100001];
static mut HELP: [i32; 100001] = [0; 100001]; // 主函数,返回最大权值和
pub fn max_sum2(n: i32, k: i32, edges: &Vec<Vec<i32>>) -> i32 {
// 构造邻接表
let mut graph = vec![vec![]; n as usize];
for edge in edges {
let a = edge[0];
let b = edge[1];
let c = edge[2];
graph[a as usize].push(vec![b, c]);
graph[b as usize].push(vec![a, c]);
}
// 初始化DP数组为-1,并递归调用dfs函数求解最大权值和
unsafe {
for i in 0..n as usize {
DP[i][0] = -1;
DP[i][1] = -1;
}
dfs(0, -1, k, &graph);
DP[0][0]
}
} // 深度优先搜索函数,计算以当前点为根节点的子树内选择一些边能够得到的最大权值和
pub fn dfs(cur: usize, father: i32, k: i32, graph: &Vec<Vec<Vec<i32>>>) {
let edges = &graph[cur];
let mut ans0 = 0; // 不选当前节点时的最大权值和
let mut ans1 = 0; // 选当前节点时的最大权值和
let mut m = 0; // HELP数组中实际存储的元素个数
unsafe {
for i in 0..edges.len() {
// 遍历当前节点的所有邻居节点
let next = edges[i][0] as usize;
if next != father as usize {
// 如果邻居不是父节点,则递归调用dfs函数
dfs(next, cur as i32, k, graph);
}
}
for i in 0..edges.len() {
// 再次遍历邻居节点,统计ans0、ans1以及HELP数组
let next = edges[i][0] as usize;
let weight = edges[i][1];
if next != father as usize {
ans0 += DP[next][0];
ans1 += DP[next][0];
if DP[next][0] < DP[next][1] + weight {
HELP[m] = DP[next][1] + weight - DP[next][0];
m += 1;
}
}
}
HELP[..m].sort_unstable_by(|a, b| b.cmp(a)); // 对HELP数组进行排序
for (i, help_i) in HELP[..m].iter().enumerate() {
// 根据HELP数组更新ans0、ans1
let cnt = i as i32 + 1;
if cnt <= k - 1 {
ans0 += help_i;
ans1 += help_i;
}
if cnt == k {
ans0 += help_i;
}
}
DP[cur][0] = ans0; // 将结果保存到DP数组中
DP[cur][1] = ans1;
}
}

2.两种算法的对数器

2.1.我们首先定义三个变量:n、v和test_times。其中,n表示节点数,v表示每个节点可能的最大权值和,test_times表示测试次数。

let n = 16;
let v = 50;
let test_times = 2000;

2.2.接着,我们使用for循环进行多次测试,每次测试随机生成一个节点数n_i32和要选取的节点数k,并使用random_edges函数生成一棵随机树的边列表。

for _ in 0..test_times {
let n_i32 = rand::thread_rng().gen_range(1, n + 1);
let k = rand::thread_rng().gen_range(1, n_i32 + 1);
let edges = random_edges(n_i32, v); // 生成随机的边

2.3.然后,我们调用max_sum1函数来计算最大权值和,并将其赋值给变量ans1。

let ans1 = max_sum1(n_i32, k, &edges);

2.4.我们接着调用优化后的解法max_sum2函数来计算最大权值和,并将其赋值给变量ans2。

let ans2 = max_sum2(n_i32, k, &edges);

2.5. 最后,我们将ans1与ans2进行比较,如果两者不相等,则说明代码存在错误,我们输出一条错误消息并退出程序。否则,我们继续进行下一轮循环。

if ans1 != ans2 {
println!("出错了!");
return;
}

2.6.在所有测试结束后,我们输出一条测试结束的消息。

println!("测试结束");

3.rust的完整代码

use rand::Rng;

// 为了测试
fn main() {
let n = 16;
let v = 50;
let test_times = 2000;
println!("测试开始");
for _ in 0..test_times {
let n_i32 = rand::thread_rng().gen_range(1, n + 1);
let k = rand::thread_rng().gen_range(1, n_i32 + 1);
let edges = random_edges(n_i32, v); // 生成随机的边
let ans1 = max_sum1(n_i32, k, &edges); // 调用暴力解法求解最大权值和
let ans2 = max_sum2(n_i32, k, &edges); // 调用优化后的解法求解最大权值和
if ans1 != ans2 {
// 如果结果不相等,则说明出错了
println!("出错了!");
return;
}
}
println!("测试结束");
} // 暴力方法
// 为了验证
fn max_sum1(n: i32, k: i32, edges: &Vec<Vec<i32>>) -> i32 {
process(edges, 0, &mut vec![false; edges.len()], n, k)
} // 递归函数,尝试选择或不选择边,返回最大权值和
fn process(edges: &Vec<Vec<i32>>, i: usize, pick: &mut Vec<bool>, n: i32, k: i32) -> i32 {
// 如果已经枚举完所有的边
if i == edges.len() {
// 统计每个点的度数,并计算当前选中边的权值和
let mut cnt = vec![0; n as usize];
let mut ans = 0;
for j in 0..edges.len() {
if pick[j] {
cnt[edges[j][0] as usize] += 1;
cnt[edges[j][1] as usize] += 1;
ans += edges[j][2];
}
}
// 判断每个点的度数是否小于等于k,如果不是则返回-1
for j in 0..n as usize {
if cnt[j] > k {
return -1;
}
}
// 返回当前选中边的权值和
return ans;
} else {
// 尝试选择当前边
pick[i] = true;
let p1 = process(edges, i + 1, pick, n, k);
// 不选择当前边
pick[i] = false;
let p2 = process(edges, i + 1, pick, n, k);
// 返回两种情况的最大值
return p1.max(p2);
}
} // 定义两个全局变量
static mut DP: [[i32; 2]; 100001] = [[0; 2]; 100001];
static mut HELP: [i32; 100001] = [0; 100001]; // 主函数,返回最大权值和
pub fn max_sum2(n: i32, k: i32, edges: &Vec<Vec<i32>>) -> i32 {
// 构造邻接表
let mut graph = vec![vec![]; n as usize];
for edge in edges {
let a = edge[0];
let b = edge[1];
let c = edge[2];
graph[a as usize].push(vec![b, c]);
graph[b as usize].push(vec![a, c]);
}
// 初始化DP数组为-1,并递归调用dfs函数求解最大权值和
unsafe {
for i in 0..n as usize {
DP[i][0] = -1;
DP[i][1] = -1;
}
dfs(0, -1, k, &graph);
DP[0][0]
}
} // 深度优先搜索函数,计算以当前点为根节点的子树内选择一些边能够得到的最大权值和
pub fn dfs(cur: usize, father: i32, k: i32, graph: &Vec<Vec<Vec<i32>>>) {
let edges = &graph[cur];
let mut ans0 = 0; // 不选当前节点时的最大权值和
let mut ans1 = 0; // 选当前节点时的最大权值和
let mut m = 0; // HELP数组中实际存储的元素个数
unsafe {
for i in 0..edges.len() {
// 遍历当前节点的所有邻居节点
let next = edges[i][0] as usize;
if next != father as usize {
// 如果邻居不是父节点,则递归调用dfs函数
dfs(next, cur as i32, k, graph);
}
}
for i in 0..edges.len() {
// 再次遍历邻居节点,统计ans0、ans1以及HELP数组
let next = edges[i][0] as usize;
let weight = edges[i][1];
if next != father as usize {
ans0 += DP[next][0];
ans1 += DP[next][0];
if DP[next][0] < DP[next][1] + weight {
HELP[m] = DP[next][1] + weight - DP[next][0];
m += 1;
}
}
}
HELP[..m].sort_unstable_by(|a, b| b.cmp(a)); // 对HELP数组进行排序
for (i, help_i) in HELP[..m].iter().enumerate() {
// 根据HELP数组更新ans0、ans1
let cnt = i as i32 + 1;
if cnt <= k - 1 {
ans0 += help_i;
ans1 += help_i;
}
if cnt == k {
ans0 += help_i;
}
}
DP[cur][0] = ans0; // 将结果保存到DP数组中
DP[cur][1] = ans1;
}
} // 为了测试
// 生成随机边的函数,返回一个包含n-1条边的vector
fn random_edges(n: i32, v: i32) -> Vec<Vec<i32>> {
let mut order = vec![0; n as usize];
for i in 0..n {
order[i as usize] = i;
}
let mut edges = vec![vec![0; 3]; (n - 1) as usize];
let mut rng = rand::thread_rng(); // 初始化随机数生成器
let mut i = n - 1;
while i >= 0 {
order.swap(i as usize, rng.gen_range(0, i + 1) as usize); // 随机交换两个位置
i -= 1;
}
for i in 1..n {
edges[(i - 1) as usize][0] = order[i as usize];
edges[(i - 1) as usize][1] = order[rng.gen_range(0, i) as usize]; // 随机选择一条边
edges[(i - 1) as usize][2] = rng.gen_range(1, v + 1); // 随机生成其权值
}
edges
}

4.运行结果

如图所示,两种算法的运行结果是一致的,说明算法没有问题。

2023-03-20:给定一个无向图,保证所有节点连成一棵树,没有环, 给定一个正数n为节点数,所以节点编号为0~n-1,那么就一定有n-1条边, 每条边形式为{a, b, w},意思是a和b之间的无的更多相关文章

  1. 2017 Wuhan University Programming Contest (Online Round) Lost in WHU 矩阵快速幂 一个无向图,求从1出发到达n最多经过T条边的方法数,边可以重复经过,到达n之后不可以再离开。

    /** 题目:Lost in WHU 链接:https://oj.ejq.me/problem/26 题意:一个无向图,求从1出发到达n最多经过T条边的方法数,边可以重复经过,到达n之后不可以再离开. ...

  2. hdu6035 Colorful Tree 树形dp 给定一棵树,每个节点有一个颜色值。定义每条路径的值为经过的节点的不同颜色数。求所有路径的值和。

    /** 题目:hdu6035 Colorful Tree 链接:http://acm.hdu.edu.cn/showproblem.php?pid=6035 题意:给定一棵树,每个节点有一个颜色值.定 ...

  3. Swift - 使用导航条和导航条控制器来进行页面切换

    通过使用导航条(UINavigationBar)与导航条控制器(UINavigationController)可以方便的在主页面和多层子页面之间切换.下面通过一个简单“组件效果演示”的小例子来说明如何 ...

  4. 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条

    http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...

  5. Bootstrap如何实现导航条?导航条实例详解

    本文主要和大家分享Bootstrap实现导航实例详解,在建设一个网站的时候,不同的页面有很多元素是一样的,比如导航条.侧边栏等,我们可以使用模板的继承,避免重复编写html代码.现在我们打算实现一个在 ...

  6. 使用原生JS+CSS或HTML5实现简单的进度条和滑动条效果(精问)

    使用原生JS+CSS或HTML5实现简单的进度条和滑动条效果(精问) 一.总结 一句话总结:进度条动画效果用animation,自动效果用setIntelval 二.使用原生JS+CSS或HTML5实 ...

  7. Android View 之进度条+拖动条+星级评论条....

    PS:将来的你会感谢现在奋斗的自己.... 学习内容: 1.进度条 2.拖动条 3.星级评论条 1.进度条...       进图条这东西想必大家是很熟悉的...为了使用户不会觉得应用程序死掉了,因此 ...

  8. 随机获取Mysql数据表的一条或多条记录

    随机获得Mysql数据表的一条或多条记录有很多方法,下面我就以users(userId,userName,password......)表(有一百多万条记录)为例,对比讲解下几个方法效率问题: sel ...

  9. 积累的VC编程小技巧之工具条和状态条

    1.工具条和状态条中控件的添加: 方法⑴.只能在ToolBar里创建控件:首先,在ToolBar中创建一个Button,其ID为ID_TOOL_COMBO(我们要将创建的控件放在该Button的位置上 ...

  10. mysql 随机获取一条或多条数据

    若要在i ≤r≤ j 这个范围得到一个随机整数r ,需要用到表达式 FLOOR( RAND() * (j – i)+i),RLOOR()取整树部分,RAND()生成0~1的随机数.ROUND(x,n) ...

随机推荐

  1. Nginx配置ThinkPHP3.1的PATHINFO模式

    location / {    if (!-e $request_filename) {         rewrite ^/(.*)$ /index.php?$1 last;         bre ...

  2. UGUI按Tab键切换输入框

    脚本挂在输入框的父物体上即可 [code]csharpcode: using System.Collections; using System.Collections.Generic; using U ...

  3. c++中文编码格式

    c++程序中涉及到中文字符的输入输出以及其他操作经常会出现乱码.乱码主要是由于程序的源文件编码.可执行文件编码以及程序运行环境的编码不匹配导致.比如,c++源程序文件编码为GB18030, 在源程序中 ...

  4. OD机试题-2022.4

    import java.util.ArrayList;import java.util.Comparator;import java.util.List;import java.util.Scanne ...

  5. Python学习笔记--图像的进一步学习

    演示地图的可视化的实现 示例: 设置全局选项 可以设置出不同的颜色,不会显得很干巴: 国内地图: 那么,我们应当如何找到相对应的颜色的编号呢? 基本步骤: 前往ab173.com网站 然后在,前端那里 ...

  6. 如何规避MyBatis使用过程中带来的全表更新风险

    作者:京东零售 贾玉西 一.前言 程序员A: MyBatis用过吧? 程序员B: 用过 程序员A: 好巧,我也用过,那你遇到过什么风险没?比如全表数据被更新或者删除了. 程序员B: 咔,还没遇到过,这 ...

  7. ASP.NET Core Web API Swagger 按标签Tags分组排序显示

    需求 swagger页面按标签Tags分组显示. 没有打标签Tags的接口,默认归到"未分组". 分组内按接口路径排序 说明 为什么没有使用GroupName对接口进行分组? 暂时 ...

  8. 百炼成钢 —— 声网实时网络的自动运维丨Dev for Dev 专栏

    本文为「Dev for Dev 专栏」系列内容,作者为声网大数据算法工程师黄南薰. 01 自动运维介绍 2016 年,Gartner 创新性地提出了 AIOps 的概念[1],开创了人工智能辅助运维决 ...

  9. 开源不易、安全慎行,中国软件如何走向文明?丨RTE 技术环境月报 202205

    各位开发者小伙伴: 这里是 2022 年第 5 期的 RTE<技术环境月报>--致力于成为对大家"有用"的 Highlight 看板--每月初通过 RTC 开发者社区( ...

  10. 大数据面试——HDFS

    一.Hadoop1.0 与 Hadoop2.0的区别