带权最大联通子树

树中的每个结点都有权重(Wi, Wi>0),编号从1~N,要求选出包含编号为1的共M个结点,使得总和最大。

基本思想:树中的动态规划。

以下转载自hihocoder:

(1)  f(t, m)表示,在以t为根的一棵树中,选出包含根节点t的m个连通的结点,能够获得的最高的评分,本题答案就是f(1, M)

(2) 转化为背包问题。针对于每一个t,同时求解它的f(t, 0..M),这样的话,可以把m视作背包容量,把每个子结点t_child都视作一件单位重量为1的物品,但是和背包问题不同的是,这件物品的总价值并不是单位价值乘以总重量,而是重量为m_child的该物品的价值为f(t_child, m_child),这样我就可以像无限背包问题一样,用这样的方法来进行求解。

(3) 采用后序遍历。这样在计算f(t,m)时,f(t_child, m_child)已经知道了。

(4) 每当遍历过一个child结点,用该结点的f(t_child, m_child )值来替换其父节点的一部分,可以保证权重值相加没有重复。

另:看过一个大神的代码,发现他存树的方式很特别。没有用传统的指针,而是用数组。每个结点对应于第一条子边(一维数组)。该边有另一头的结点和其兄弟边。我模仿他的方法,用数组存树这个结构。首先第i个结点可以直接找到他的第一个孩子child[i],每个结点存储它相邻的一个兄弟结点brother[i]。则比如结点1有孩子结点234,则child[1...4]={2,0,0,0},brother[1...4]={0,3,4,0}。由于本题编号固定,用下标存取效率快。



#include <stdio.h>
#include <string.h>


#define MAX(a,b) ((a)>(b))?(a):(b);
int f[101][101];// 0<=M<=N<=100 f[N][M]
int child[101];//the first child node
int brother[101];//brother node
int N=0;
int M=0;


void printArray(){
printf("test:\n");
for(int i=0; i<=N; i++ ){
printf("%d ",i);
}
for(int i=0; i<=N; i++ ){
printf("%d ",child[i]);
}
for(int i=0; i<=N; i++ ){
printf("%d ",brother[i]);
}
}


void calculateF( int node, int childNode ){
for( int m = M; m>=2; m-- ){
for( int child_m = 1; child_m<m; child_m++ ){
f[node][m] = MAX( f[node][m], f[node][m-child_m]+f[childNode][child_m] );
}
}
}


void dfs(int node){
int childNode = child[node];
while( childNode ){
dfs(childNode);
calculateF( node, childNode );
childNode = brother[childNode];
}
}


int main(){
scanf("%d%d", &N, &M);
memset(f,0,sizeof(f));
memset(child,0,sizeof(child));
memset(brother,0,sizeof(brother));
for(int i=1; i<=N; i++ )//N个结点的值
scanf( "%d",&f[i][1] );
int n1=0,n2=0;
for(int i=1; i<N; i++ ){//N-1条边
scanf("%d%d",&n1,&n2);
if( child[n1]==0 )
child[n1]=n2;
else{
n1=child[n1];
while( brother[n1]!=0 )
n1= brother[n1];
brother[n1] = n2;
}
}
//test
//printArray();


dfs(1);
printf("%d\n",f[1][M]);
}

### 关于最大生成树 在图论中,最大生成树(Maximum Weight Spanning Tree, MWST)是指在一个加连通无向图 \( G = (V, E) \) 中找到一棵包含所有顶点的生成树,其边重之和达到最大化。与最小生成树类似,最大生成树也满足生成树的基本性质:它是原图的一个极小连通子图,并且包含所有的顶点[^2]。 #### 最大生成树的算法 构建最大生成树可以通过修改经典的最小生成树算法来实现。以下是两种常见的算法及其调整方式: 1. **克鲁斯卡尔算法(Kruskal Algorithm)** - 原理:按照边重降序排列所有边,依次选取不会形成环的最大重边加入生成树。 - 修改:只需将原本按升序排序改为按降序排序即可。 - 时间复杂度:\( O(E \log E) \),其中 \( E \) 是边的数量。 2. **普里姆算法(Prim Algorithm)** - 原理:从任意一个起点开始,逐步扩展当前已有的部分生成树,每次选择连接未访问顶点的最大重边。 - 修改:在更新候选边时,选择最大重而非最小的重。 - 时间复杂度:\( O(V^2) \) 或 \( O((E + V) \log V) \),具体取决于实现方式。 #### 实现示例 以下是以 Python 编写的基于 Kruscal 算法的最大生成树实现代码: ```python class UnionFind: def __init__(self, size): self.parent = list(range(size)) def find(self, u): while u != self.parent[u]: self.parent[u] = self.parent[self.parent[u]] u = self.parent[u] return u def union(self, u, v): root_u = self.find(u) root_v = self.find(v) if root_u != root_v: self.parent[root_u] = root_v def max_spanning_tree_kruskal(edges, num_vertices): edges.sort(key=lambda edge: edge[2], reverse=True) # 按照重降序排序 uf = UnionFind(num_vertices) mst_weight = 0 result_edges = [] for u, v, weight in edges: if uf.find(u) != uf.find(v): # 如果不形成环 uf.union(u, v) mst_weight += weight result_edges.append((u, v, weight)) return result_edges, mst_weight # 测试用例 edges = [ (0, 1, 7), (0, 3, 5), (1, 2, 8), (1, 3, 9), (2, 3, 6), (2, 4, 4), (3, 4, 3) ] num_vertices = 5 result, total_weight = max_spanning_tree_kruskal(edges, num_vertices) print("最大生成树的边:", result) print("总重:", total_weight) ``` 此代码通过 `Union-Find` 数据结构检测并避免环路,从而确保所选边能够组成一棵合法的生成树。 --- #### 性质对比 | 特性 | 最小生成树 | 最大生成树 | |-------------------|----------------------------|-----------------------------| | 边的选择标准 | 重最小 | 最大 | | 应用场景 | 成本最低化 | 收益最大化 | | 是否唯一 | 不一定 | 同样不一定 | 尽管两者的目标相反,但在实际应用中均需考虑如何高效地解决问题以及可能存在的多种解的情况[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值