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. 关于tomcat部署web服务方式

    方式1.apache-tomcat-8.0.47\webapps文件夹下放war包会自动解压.文件夹名称就是访问路径 方式2.apache-tomcat-8.0.47\conf\Catalina\lo ...

  2. ls和mkdir

    ls  查看当前目录的内容 1) ls -l 显示详细列表 2) Ls -lh 吧文件大小以人性化的方式展开 3) ls -a 显示所有的文件,包括隐藏文件,隐藏文件是已.开头的文件 4) ll 等于 ...

  3. VS工具显示小技巧,显示内联参数

    工具---选项---文本编辑器---C#---高级---在显示内联参数名称提示前面打勾. 则可以在代码中看到参数提示信息.

  4. 手把手教你基于luatos的4G(LTE Cat.1)模组接入华为云物联网平台

    摘要:本期文章采用了4G LTE Cat.1模块,编程语言用的是lua,实现对华为云物联网平台的设备通信与控制 本文分享自华为云社区<基于luatos的4G(LTE Cat.1)模组接入华为云物 ...

  5. Android笔记--外部存储空间

    存储文件的操作 外部存储空间 私有存储空间和公共存储空间 外部存储空间分为私有+公有 保存文件到外部存储空间的相关代码操作: 私有空间: 公有空间: 记得增加权限(Android_Manifest.x ...

  6. 对Android关联SDK后,还是无法显示那俩图标的解决

    显示出来!!!! 可以这么解决: 步骤一: 步骤二: 找到这个,在上方的栏里面: 步骤三: 将这四个选中: 步骤四: 然后选中这个栏: 步骤五: 选中Android: 步骤六: 最后,点击右下角的Ap ...

  7. docker 部署 postgres

    1. 打开dockerhub查找postgres版本 地址 https://registry.hub.docker.com/_/postgres/tags 2.复制需要的版本 docker pull ...

  8. singleflight 使用记录以及源码阅读

    singleflight 使用方法以及源码阅读 1.简介 安装方式: go get -u golang.org/x/sync/singleflight singleflight 是Go官方扩展同步包的 ...

  9. java多线程--2 静态代理、Lambda表达式

    java多线程--2 静态代理.Lambda表达式 静态代理 package com.ssl.demo02; //静态代理 //真实对象和代理对象都要实现同一个接口 //代理对象必须要代理真实角色 / ...

  10. 两条命令搞定 ChatGPT API 的调用问题

    自2022年11月30日 OpenAI 发布 ChatGPT 以来,虽然时有唱衰的声音出现,但在OpenAI不断推陈出新,陆续发布了OpenAPI.GPT-4.ChatGPT Plugins之后,似乎 ...