给树染色(0x07 贪心)

简介: 笔记

给树染色


题意

一颗树有 n 个节点,这些节点被标号为:1,2,3…n,每个节点 ii 都有一个权值 A[i]。


现在要把这棵树的节点全部染色,染色的规则是:


根节点 R 可以随时被染色;对于其他节点,在被染色之前它的父亲节点必须已经染上了色。


每次染色的代价为 T×A[i],其中 TT 代表当前是第几次染色。


求把这棵树染色的最小总代价。


思路

比较容易想到的贪心思路为,每次找到还未染色的权值最大的节点进行染色。但是对于这样的一棵树:有一个节点的权值较小,但是它的子节点权值都非常大,另一个节点的权值较大,但是没有子节点。上述的贪心算法显然不是最优解。


题目中提到,一个节点被染色之前其父亲节点必须已经染上了色。我们可以想到类似于上述贪心算法的一个算法:若一个节点被染色了,那么它的权值最大的子节点一定在它之后被染色。


假设一个节点为 a 其权值最大的子节点为 b ,另外一个节点为 c 那么有以下两种染色方案。


先给a b染色,再给 c 染色


代价为:a + 2*b + 3*c


先给 c 染色,再给a b 染色


代价为 c + 2*a + 3*b


(a + 2*b + 3*c) - (c + 2*a + 3*b) = 2*c - (a + b)50.png

所以我们在考虑点的染色顺序时,将 a b 看成一个点,其权值为 a b 的均值。


进一步推广:有两组点 a 1 , a 2 , a 3 … a n 和 b 1 , b 2 , b 3 … b n

当第一组点的权值(平均值)大于第二组的权值(平均值)时,先染第一组点。


所以最终做法为:每次找出当前权值最大的非根节点,将其染色顺序排在其父节点之后的位置,然后将该点合并进父节点,并更新父节点的权值,直到将所有的点都合并进根节点为止。


在进行点的合并时进行权值的更新。例如有两组点x , y 将 x合并进 y中,因为 x 中的所有点都排在 y中的最后一个点后染色,所以 x 中点的权值都要乘以一个偏移量 k,k 为 y 中 点的数量。


代码

#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define mod 1000000007
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
const int N = 1000 + 10;
int n, root;
struct Node {
  int p; // 当前节点的父亲节点
  int s; // 点集的大小 
  int v; //当前点集的权值
  double avg; //当前点集的平均值
}node[N];
int find() { // 找到当前还未染色的平均值最大的点集
  int res = 0;
  double avg = 0.0;
  for (int i = 1; i <= n; ++i) {
    if (i != root && node[i].avg > avg) {
      avg = node[i].avg;
      res = i;
    }
  }
  return res;
}
void solve() {
  cin >> n >> root;
  int ans = 0;
  for (int i = 1; i <= n; ++i) {
    cin >> node[i].v;
    node[i].avg = node[i].v;
    node[i].s = 1;
    ans += node[i].v; //由题意,肯定是根节点作为第一个染色的节点,剩下的点的染色顺序全部排在根节点之后,所以也会产生一个偏移量为根节点的点集大小,为1,所以要加上所有非根节点的权值,根节点为第一个染色的,所以也要加上根节点的权值,即每个点的权值都要加上一次。
  }
  for (int i = 1; i <= n - 1; ++i) {// 记录点的父子关系
    int a, b; cin >> a >> b;
    node[b].p = a;
  }
  for (int i = 1; i <= n - 1; ++i) {
    int p = find(); // 找到当前平均值最大的点
    int father = node[p].p; // 平均值最大的点的父节点
    ans += node[p].v * node[father].s; // 答案加上自己点的权值 * 偏移量
    node[p].avg = -1; // 置成 -1 相当于标记已经染色过了
    for (int j = 1; j <= n; ++j) { 
      if (node[j].p == p)
        node[j].p = father;
    }
    // 将子节点与父节点缩成一个点
    node[father].v += node[p].v;
    node[father].s += node[p].s;
        //更新缩点后的平均权值
    node[father].avg = (double)node[father].v / node[father].s; 
  }
  cout << ans << endl;
}
signed main() {
  // int t; cin >> t;
  // while (t--)
    solve();
  return 0;
}



目录
相关文章
|
Linux
阿里云官方yum源
阿里云官方yum源
73725 0
|
9月前
|
存储 人工智能 运维
超大规模云网络技术新突破!阿里云斩获中国自动化学会科技进步一等奖
超大规模云网络技术新突破!阿里云斩获中国自动化学会科技进步一等奖
388 60
Vue3中getCurrentInstance如何与ts结合使用
【8月更文挑战第16天】Vue3中getCurrentInstance如何与ts结合使用
471 2
Vue3中getCurrentInstance如何与ts结合使用
|
缓存 NoSQL 关系型数据库
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
本文深入探讨了Redis缓存的相关知识,包括缓存的概念、使用场景、可能出现的问题(缓存预热、缓存穿透、缓存雪崩、缓存击穿)及其解决方案。
893 0
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
|
Java 大数据 分布式数据库
Spring Boot 与 HBase 的完美融合:探索高效大数据应用开发的新途径
【8月更文挑战第29天】Spring Boot是一款广受好评的微服务框架,以其便捷的开发体验著称。HBase则是一个高性能的大数据分布式数据库系统。结合两者,可极大简化HBase应用开发。本文将对比传统方式与Spring Boot集成HBase的区别,展示如何在Spring Boot中优雅实现HBase功能,并提供示例代码。从依赖管理、连接配置、表操作到数据访问,Spring Boot均能显著减少工作量,提升代码可读性和可维护性,使开发者更专注业务逻辑。
842 1
|
10月前
|
运维 监控 安全
出海短信就选阿里云,覆盖 200+国家
国际/港澳台短信,就找阿里云
336 3
|
监控 安全 API
什么是 API?
API 即应用程序编程接口(Application Programming Interface),它是一组定义了软件组件之间如何交互的规则和协议。可以将 API 想象成一个服务的 “窗口”,通过这个窗口,不同的软件程序可以相互通信、共享数据和功能。 例如,当你使用手机上的天气应用时,这个应用可能会通过调用气象部门提供的 API 来获取实时的天气数据。
10894 12
|
11月前
|
Java 数据安全/隐私保护 开发者
【潜意识Java】深入理解 Java 面向对象编程(OOP)
本文介绍了Java中的面向对象编程(OOP)核心概念,包括封装、继承、多态和抽象。封装通过访问控制保护数据,提高安全性;继承支持代码复用,减少冗余;多态实现灵活的行为调用;抽象则隐藏细节,简化接口设计。掌握这些概念有助于编写高效、灵活且易于维护的代码。文章通过实例详细讲解了每个概念在Java中的应用,并总结了它们的优势。
554 3
|
负载均衡 Dubbo Java
Dubbo 挂载到 Spring Cloud 注册中心
【2月更文挑战第12天】Dubbo 挂载到 Spring Cloud 注册中心
249 7