CF 1039D You Are Given a Tree && CF1059E Split the Tree 的贪心解法[通俗易懂]

CF 1039D You Are Given a Tree && CF1059E Split the Tree 的贪心解法[通俗易懂]CF 1039D You Are Given a Tree && CF1059E Split the Tree 的贪心解法

大家好,又见面了,我是你们的朋友全栈君。

1039D 题意:

给你一棵树,要求对给定链长于 k = 1, 2, 3, …, n,求出最大的链剖分。

1059E 题意:

给你一棵带权树,要求对于一组给定的 L, W 求出最小完全竖链剖分满足每条链点数不超过 L,权值和不超过 W。

显然两题是有共同点的,就是让我们求满足一定条件的树的最值链剖分。

比较暴力的可以尝试用 DP 计数,但是我不想深入 DP,因为方程比较复杂,思考起来不太容易。

很巧的是,这两题可以用相似的贪心思想来做。

在思考具体细节之前,需要明确贪心的主要思想:在从下往上回溯的过程中,总是在合适条件下贪心地成链。

1039D 

如果对于给定的 k 值可以快速求解,就可以用分块的思想处理 k 不同时的情况。

怎样求解给定的 k 呢?

还是贪心的做法。

在 dfs 回溯过程中,一旦当前点可以成链,就直接钦定下来 :)

具体操作过程中,需要对每个点记录最长与次长子链,这样一旦两者和达到 k,就可以成链了。

证明略。

#include <bits/stdc++.h>

using namespace std;

const int N = 100000 + 5;
const int BLOCK = 100;

int n;
int f[N][2];
vector<int> node;
vector<int> g[N];
int fa[N];

void dfs(int u, int f = 1) {
  for (auto v: g[u]) {
    if (v != f) {
      dfs(v, u);
    }
  }
  node.push_back(u);
  fa[u] = f;
}

int solve(int k) {
  for (int i = 1; i <= n; i++) {
    f[i][0] = f[i][1] = 0;
  }
  int ret = 0;
  for (int u: node) {
    if (f[u][0] + f[u][1] + 1 >= k) {
      ret++;
    } else {
      if (f[fa[u]][0] < f[u][0] + 1) {
        f[fa[u]][1] = f[fa[u]][0];
        f[fa[u]][0] = f[u][0] + 1;
      } else {
        f[fa[u]][1] = max(f[fa[u]][1], f[u][0] + 1);
      }
    }
  }
  return ret;
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    g[u].push_back(v);
    g[v].push_back(u);
  }
  dfs(1);
  int x = N, k = 1;
  while (x > BLOCK) {
    x = solve(k++);
    printf("%d\n", x);
  }
  for (int i = BLOCK; i >= 0; i--) {
    if (solve(k) != i || k > n) {
      continue;
    }
    int l = k, r = n + 1;
    while (r - l > 1) {
      int mid = (l + r) / 2;
      if (solve(mid) == i) {
        l = mid;
      } else {
        r = mid;
      }
    }
    while (k <= l) {
      printf("%d\n", i);
      k++;
    }
  }
  return 0;
}

1059E

与上题不同的是,这题的链要求是竖直的,考虑从链底做贪心。

对于每个点,关注每条从子节点过来的链,并且贪心的选择将当前点并入终点最高的链上。

如果没有这样的链,就直接根据题目条件选择最高的链。

#include <bits/stdc++.h>

using namespace std;

const int N = 100000 + 5;

int up[N];
int dep[N], path[N];
int fa[N][21];
int n, L;
int w[N];
long long S;
long long sumw[N];
vector<int> g[N];

void prepare(int u, int f = 0) {
  dep[u] = dep[f] + 1;
  sumw[u] = sumw[f] + w[u];
  up[u] = u;
  fa[u][0] = f;
  for (int i = 1; i <= 20; i++) {
    fa[u][i] = fa[fa[u][i-1]][i-1];
  }
  int lim = L-1;
  for (int i = 20; i >= 0; i--) {
    if (fa[up[u]][i] != 0 && (1 << i) <= lim && sumw[u] - sumw[fa[fa[up[u]][i]][0]] <= S) {
      up[u] = fa[up[u]][i];
      lim -= (1 << i);
    }
  }
  for (int v: g[u]) {
    prepare(v, u);
  }
}

int solve(int u) {
  int ret = 0, best = -1;
  for (int v: g[u]) {
    ret += solve(v);
    if (path[v] != v) {
      if (best == -1 || dep[path[v]] < dep[best]) {
        best = path[v];
      }
    }
  }
  if (best == -1) {
    ret++;
    best = up[u];
  }
  path[u] = best;
  return ret;
}

int main() {
  scanf("%d %d %lld", &n, &L, &S);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &w[i]);
    if (w[i] > S) {
      printf("-1\n");
      return 0;
    }
  }
  for (int i = 2; i <= n; i++) {
    int p;
    scanf("%d", &p);
    g[p].push_back(i);
  }
  prepare(1);
  printf("%d\n", solve(1));
  return 0;
}

转载于:https://www.cnblogs.com/HailJedi/p/9774769.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107263.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • openssl安装方式(Ubuntu下)

    openssl安装方式官方网站1、解压2、编译安装3、生成软连接4、测试官方网站https://www.openssl.org/source/1、解压我这里安装的版本时1.0.2,其实都一样,默认版本是1.1.1拿到源码后先解压源码文件openssl-1.0.2u.tar.gz2、编译安装进入源码目录:cdopenssl-1.0.2u指定安装路径编译安装sudo./config–prefix=/usr/local/opensslsudomake

    2022年4月7日
  • 流水线设计的概念_流水线原理

    流水线设计的概念_流水线原理流水线设计的概念它是面积换取速度思想的又一种具体现。所谓流水线设计实际上是把规模较大、层次较多的组合逻辑电路分为几个级,在每一级插入寄存器并暂存中间数据。流水线处理是提高组合逻辑设计的处理速度和吞吐量的常用手段。如果某个组合逻辑设计的处理流程可以分为若干步骤,而且整个数据处理过程是“单流向”的,即没有反馈或者迭代运算,前一个步骤的输出是下一个步骤的输入,则可以考虑采用流水线设计方法提高数据处理频率,即吞吐量。……

    2022年8月14日
  • 彻底弄懂StringBuffer与StringBuilder的区别「建议收藏」

    彻底弄懂StringBuffer与StringBuilder的区别「建议收藏」一问道StringBuffer与StringBuilder的区别,张口就来StringBuffer是线程安全的,因为它相关方法都加了synchronized关键字,StringBuilder线程不安全。没错,确实如此,但是我们查看过源码会发现StringBuffer是从jdk1.0就开始了,StringBuilder是从jdk1.5开始的。于是我就产生这样一个疑问,既然已经有了StringBu…

    2022年6月28日
  • AssertValid函数学�

    AssertValid函数学�

    2021年12月8日
  • ping命令详解 ping命令入门详解

    ping命令详解 ping命令入门详解在这个时代,科技越来越发达,网络已经越来越成为人们不可缺少的一部分。计算机也已经是很多学校的课程了,因为计算机技术是非常有技术性的专业,它其中涉及到很多专业知识,需要通过学习才能掌握。今日小编就为大家介绍一个计算机的命令,它叫做Ping,这边介绍一下它的入门知识,主要是关于ping连接和命令方面的介绍。  1、Ping的基础知识  ping命令相信大家已经再熟悉不过了,但是能把ping的功能发…

    2022年9月22日
  • 如何理解95%置信区间_95的置信区间和90的置信区间

    如何理解95%置信区间_95的置信区间和90的置信区间1.点估计与区间估计首先我们看看点估计的含义:是用样本统计量来估计总体参数,因为样本统计量为数轴上某一点值,估计的结果也以一个点的数值表示,所以称为点估计。点估计虽然给出了未知参数的估计值,但是未给出估计值的可靠程度,即估计值偏离未知参数真实值的程度。接下来看下区间估计:给定置信水平,根据估计值确定真实值可能出现的区间范围,该区间通常以估计值为中心,该区间则为置信区间。2.中心…

    2022年9月23日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号