HAOI树上染色

Description :

有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。

Solution:

这道题的思路很好,数据较强,可以看出这是道树形dp
怎么设计状态呢?dp[x][j]表示以x为根的子树有j个点染成黑色的最大收获
然后,然后就GG了
原来这道题是考虑贡献,强啊
状态改为以x的根的子树,有j个点染成黑色的最大贡献,这个贡献是对这个棵树而言的
转移比较明显 枚举 j ,枚举子节点 的 黑色点数 k,直接dp即可
注意枚举顺序,我们需要dp[x][j - k]未更新的贡献,就像01背包,一样倒序枚举j,正序枚举k,用j-k更新j,从更小的更新,它的阶段一定是上一个,不会出现自己更新自己,具有拓扑性
注意边界:f[x][0]=f[x][1]=0;,不会出现成对的所以没有贡献

#include <cstring>
#include <cstdio>
#include <iostream>

using namespace std;

const int MAXX = 2020;

long long f[MAXX][MAXX];
int hed[MAXX], ver[MAXX << 1], nxt[MAXX << 1], edge[MAXX << 1], siz[MAXX];
int n, cnt, tot;

inline void add(int x, int y,int z) {
  ver[++tot] = y;
  nxt[tot] = hed[x];
  hed[x] = tot;
  edge[tot] = z;
}
void dfs(int x, int fa) {
  siz[x] = 1;
  f[x][0] = f[x][1] = 0;
  for (int i = hed[x]; i; i = nxt[i]) {
    int y = ver[i];
    if (y == fa) continue;
    dfs(y,x);
    siz[x] += siz[y];
  }
  for (int i = hed[x]; i; i = nxt[i]) {
    int y = ver[i];
    if (y == fa) continue;
    for (int j = min(siz[x],cnt); j >= 0; --j) {
      for (int k = 0;k <= min(siz[y], j); ++k) {
        if(f[x][j - k] < 0) continue;
        long long val = (long long)k * (cnt - k) * edge[i] + (long long)(siz[y] - k) * (n - cnt - siz[y] + k) * edge[i];
        f[x][j] = max(f[x][j], f[x][j - k] + f[y][k] + val);
      }
    } 
  }
}
int main() {
  scanf("%d%d", &n, &cnt);
  for (int i = 1; i < n; ++i) {
    int x, y, z;
    scanf("%d%d%d",&x, &y, &z);
    add(x, y, z);
    add(y, x, z);
  }
  memset(f,-1,sizeof(f));
  dfs(1, 0);
  printf("%lld",f[1][cnt]);
  return 0;
}

%%%
syt我是巨佬

posted @ 2018-10-19 19:13  ART_coder  阅读(144)  评论(0编辑  收藏  举报