• 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
    ", 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
    ", 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
    ");
          return 0;
        }
      }
      for (int i = 2; i <= n; i++) {
        int p;
        scanf("%d", &p);
        g[p].push_back(i);
      }
      prepare(1);
      printf("%d
    ", solve(1));
      return 0;
    }
  • 相关阅读:
    更新我电脑的编译器之Java语言
    HTML/CSS基础
    查找元素的杀手锏xpath
    错误日志的实时抓取保证代码质量
    Splinter常用api
    从底层向上理解Git
    infer运用实践
    流程图在测试用例编写中的运用
    2016小结
    Splinter 查找元素
  • 原文地址:https://www.cnblogs.com/HailJedi/p/9774769.html
Copyright © 2020-2023  润新知