树形DP-Apple Tree

本文深入解析了一道复杂的数据结构题目,通过树形动态规划(DP)算法解决节点权值与最小公倍数的关系问题。文章详细阐述了如何使用递归方式更新节点的权值最大值(mx)和倍数计数(cnt),并利用最小公倍数(LCM)和最大公约数(GCD)进行节点权值的计算。通过对子节点的分析,确定了当前节点的权值必须是其所有子节点权值最小公倍数的倍数这一关键特性。

题目传送门
(详细思路)思路参见:https://www.cnblogs.com/fightfordream/p/6653890.htm
这道题想了好久才想明白。。。。。。做这种难度的题对自己还是很有收获的。
/*
个人理解 :首先 这道题要每个节点的权值是x的倍数。因为每个节点都要相等,所以有倍数关系,那么
当前节点u的权值必然是其子节点倍数的最小公倍数。比如其子节点为2n == 4m == 3k;而sum = 2n+3k+4m
所以每个子节点的权值是lcm(2,3,4)的最小公倍数的倍数。这道题用的就是cnt数组记录
还要记录每个节点当前的最大权值。所以用mx。
这道题递归求解,所以分析问题时,只要考虑当前节点即可(举一个3层的树分析),其他的递归求解。
*/

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<map>
#include<cstring>
#include<string>
#include<cmath>

using namespace std;

typedef long long LL;

#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define pii pair<int,int>
#define all(x) x.begin(),x.end()
#define mem(a,b) memset(a,b,sizeof(a))
#define per(i,a,b) for(int i = a;i <= b;++i)
#define rep(i,a,b) for(int i = a;i >= b;--i)
const int maxn = 1e5;
int n = 0,m = 0;
int w[maxn+10];
vector<int> vt[maxn+10];
LL mx[maxn+10];//mx[i]表示节点i权值的最大值 
LL cnt[maxn+10];//cnt[i]表示节点i的权值必须是cnt[i]的倍数 
/*
个人理解 :首先 这道题要每个节点的权值是x的倍数。因为每个节点都要相等,所以有倍数关系,那么
当前节点u的权值必然是其子节点倍数的最小公倍数。比如其子节点为2n == 4m == 3k;而sum = 2n+3k+4m
所以每个子节点的权值是lcm(2,3,4)的最小公倍数的倍数。这道题用的就是cnt数组记录 
还要记录每个节点当前的最大权值。所以用mx。
这道题递归求解,所以分析问题时,只要考虑当前节点即可(举一个3层的树分析),其他的递归求解。 
*/
LL gcd(LL a,LL b){
	return b == 0 ? a : (gcd(b,a%b));
}
LL lcm(LL a,LL b){
	return a/gcd(a,b) * b;
}
void dfs(int curr,int fa){
	int num = 0;
	int size = vt[curr].size();
	for(int i = 0;i <= size-1;++i){
		int v = vt[curr][i];
		if(v == fa){
			continue;
		}
		dfs(v,curr);
		if(num == 0){//刚开始赋值 
			mx[curr] = mx[v];
				cnt[curr] = cnt[v];	
		}else{
			if(cnt[curr] < 1e14){//有溢出的风险 
				cnt[curr] = lcm(cnt[curr],cnt[v]);
			}	
			mx[curr] = min(mx[curr],mx[v]) / cnt[curr] * cnt[curr];
		}
		++num;
	}
	if(num == 0){
		mx[curr] = w[curr];	cnt[curr] = 1;
	}else{
		mx[curr] *= num;	
		if(cnt[curr] < 1e14){
			cnt[curr] *= num;
		}
	}
}
int main(){
	while(~scanf("%d",&n)){
		LL ans = 0;
		per(i,1,n){
			scanf("%d",&w[i]);
			ans += w[i];
		} 
		per(i,1,n-1){
			int u = 0,v = 0;
			scanf("%d %d",&u,&v);
			vt[u].push_back(v);vt[v].push_back(u);
		}	
		dfs(1,-1);
		printf("%I64d\n",ans - mx[1]);
	}
	
	return 0;
}

### 计数问题解决方案及其涉及的算法与数据结构 #### 一、计数问题概述 计数问题是计算科学领域中一类常见的问题,通常涉及到统计某些特定条件下的对象数量。这类问题的核心在于如何高效地遍历和统计满足条件的对象。为了优化性能,往往需要借助合适的 **数据结构** 和 **算法** 来解决问题。 --- #### 二、常用的数据结构支持计数操作 1. **哈希表 (Hash Table)** 哈希表是一种基于键值映射的数据结构,能够快速完成查找、插入和删除操作。对于计数问题而言,可以利用哈希表来记录不同元素出现的次数。例如,在 Python 中可以通过 `dict` 或者 `collections.Counter` 实现这一功能[^3]。 ```python from collections import Counter data = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'] counter = Counter(data) print(counter) # 输出: Counter({'apple': 3, 'banana': 2, 'orange': 1}) ``` 2. **数组 (Array)** 如果待处理的数据范围较小且连续,则可以直接使用数组作为计数器。通过将数值映射到数组下标的方式,可以在 O(1) 时间内更新计数值[^1]。 ```python max_value = 10 counts = [0] * (max_value + 1) numbers = [2, 5, 2, 8, 5, 9] for num in numbers: if 0 <= num <= max_value: counts[num] += 1 print(counts) # 输出: [0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 1] ``` 3. **树形结构 (Tree Structure)** 对于动态区间查询或者大规模数据集上的频繁增删改查场景,平衡二叉搜索树(如红黑树)或线段树可能是更好的选择。这些高级数据结构允许我们以较低的时间复杂度维护全局状态并执行局部调整[^2]。 --- #### 三、典型算法应对策略 1. **暴力枚举法** 这是最基础也是最容易理解的一种方法,适用于小型数据规模的情况。它通过对整个数据集合逐一扫描的方式来获取符合条件的结果数目。然而由于其较高的时间开销(O(n²)),并不适合实际应用中的大数据环境。 2. **分治思想的应用** 当面对复杂的多维约束条件下求解最优解时,“分而治之”的理念显得尤为重要。比如经典的快速排序过程就体现了这种递归分解再组合的思想模式。同样地,在解决一些特殊的排列组合类别的计数难题上也可以采用类似的思路进行拆解分析。 3. **动态规划技术** 动态规划(Dynamic Programming),简称DP,主要用于寻找具有重叠子问题特性的最优化路径决策序列等问题解答方案之一。在很多情况下,如果一个问题能被划分为若干个更小相同类型的子问题,并且这些问题之间存在一定的依赖关系的话,那么就可以考虑运用此技巧提高运算效率降低冗余重复劳动成本。 --- #### 四、实例演示 假设我们需要在一个整型数组里找出所有两数相加等于给定目标值的配对总数: ```python def count_pairs(nums, target): seen = {} result = 0 for num in nums: complement = target - num if complement in seen: result += seen[complement] seen[num] = seen.get(num, 0) + 1 return result example_list = [1, 5, 7, -1, 5] target_sum = 6 print(f"Number of pairs with sum {target_sum}: {count_pairs(example_list, target_sum)}") # Output should be 3. ``` 上述代码片段展示了如何利用哈希表有效减少嵌套循环带来的额外负担从而达到加速目的的同时保持清晰易懂的设计风格。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值