BZOJ1369 [Baltic2003] Gem

2018 年 12 月 26 日发布.

Description

给出一棵 $n$ 个结点的树,要求给每个点一个正整数权值,使得相邻的结点的权值不相等,且最小化权值和。

$n\leqslant10^4$。

Solution

考虑令 $f_{i,j}$ 表示 $i$ 结点标成 $j$ 的时候其子树最小权值和。

$$f_{i,j}=\sum_k\min_{t\neq j}f_{k,t}$$

其中 $k$ 取遍 $i$ 的儿子。可以发现对每个 $i$,所有 $f_{i,j}$ 中只有最小值和次小值可能用得到。

所以记录最小值、最小值对应的 $j$ 和次小值即可。复杂度 $O(n)$。

Code

#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>

const int N = 10050;
const int INF = N * N;

int n, pre[N], nxt[N * 2], to[N * 2], cnt;

int readInt() {
  int ans = 0, c, f = 1;
  while (!isdigit(c = getchar()))
    if (c == '-') f = -1;
  do ans = ans * 10 + c - '0';
  while (isdigit(c = getchar()));
  return ans * f;
}

inline void addEdge(int x, int y) {
  nxt[cnt] = pre[x];
  to[pre[x] = cnt++] = y;
  nxt[cnt] = pre[y];
  to[pre[y] = cnt++] = x;
}

struct Msg {
  int m1, m1v, m2v;
  Msg() {}
  void upd(int x, int v) {
    if (v < m1v) {
      std::swap(x, m1);
      std::swap(v, m1v);
    }
    if (v < m2v)
      m2v = v;
  }
} F[N];

int C[N];
bool vis[N];

void Solve(int x, int f) {
  int S = 0;
  Msg &ans = F[x];
  ans.m1v = ans.m2v = INF;
  for (int i = pre[x]; ~i; i = nxt[i]) if (to[i] != f)
    Solve(to[i], x);
  for (int i = pre[x]; ~i; i = nxt[i]) if (to[i] != f) {
    Msg &t = F[to[i]];
    S += t.m1v;
    C[t.m1] += t.m2v - t.m1v;
    vis[t.m1] = true;
  }
  int k = 1;
  while (vis[k]) ++k;
  ans.upd(k, S + k);
  while (vis[++k]);
  ans.upd(k, S + k);
  for (int i = pre[x]; ~i; i = nxt[i]) if (to[i] != f) {
    Msg &t = F[to[i]];
    if (!vis[t.m1]) continue;
    ans.upd(t.m1, S + C[t.m1] + t.m1);
    vis[t.m1] = false;
    C[t.m1] = 0;
  }
}

int main() {
  n = readInt();
  memset(pre, -1, sizeof pre);
  for (int i = 1; i < n; ++i) addEdge(readInt(), readInt());
  Solve(1, 0);
  printf("%d\n", F[1].m1v);
  return 0;
}