1. 程式人生 > >CF 1039D You Are Given a Tree && CF1059E Split the Tree 的貪心解法

CF 1039D You Are Given a Tree && CF1059E Split the Tree 的貪心解法

scanf sum turn namespace 試用 sin 這樣的 include clu

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;
}

CF 1039D You Are Given a Tree && CF1059E Split the Tree 的貪心解法