【香蕉OI】商贸往来【HAOI 2015】 树上染色(树型 DP)

文章目录

题意

一棵 n n n 个点的树,选 m m m 个黑点,其余白点,最大化两两黑点之间距离和两两白点之间距离之和。

思路

DP 很好想, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 i i i 为根的子树中选了 j j j 个黑点的情况下,这棵子树中的黑点和白点对答案的贡献。

因为子树内部有 j j j 个黑点,那么外部就有 m − j m-j mj 个黑点,那么内部黑点的贡献就是内部两两距离和加上所有黑点到 i i i 的距离乘上外部黑点个数。很好转移。

主要是如何证明这个看似 O ( n 3 ) O(n^3) O(n3) 的 DP 是 O ( n 2 ) O(n^2) O(n2) 的。这里摘抄一下 扶苏的博客

发现对于每个点,做的工作相当于先把 i i i 看做一个点,然后枚举每个儿子,做 O ( s i z [ i ] ∗ s i z [ s o n ] ) O(siz[i]*siz[son]) O(siz[i]siz[son]) 的工作,把这个儿子接到 i i i 上。

而这个复杂度可以看作枚举 s o n son son 中的一个点和之前的 i i i 的子树中的一个点。发现每一个点对只可能在他们的 LCA 处枚举到,所以是 O ( n 2 ) O(n^2) O(n2) 的。

你以为你打的是暴力???

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
const int N = 5000 + 10, M = N<<1;
int n, k, siz[N];
int h[N], ecnt, v[M], w[M], nxt[M];
LL dp[N][N];
 
template<class T>inline void read(T &x){
    x = 0; bool fl = 0; char c = getchar();
    while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
    while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
    if (fl) x = -x;
}
 
void add(int x, int y, int z){
    v[++ecnt] = y; w[ecnt] = z; nxt[ecnt] = h[x];
    h[x] = ecnt;
}
 
namespace Solver1
{
    void dfs(int u, int fa)
    {
        siz[u] = 1;
        for (int i = h[u]; i; i = nxt[i])
            if (v[i] != fa){
                dfs(v[i], u);
                for (int o = min(k, siz[u]+siz[v[i]]); o >= 0; -- o)
                    for (int p = max(o-siz[u], 0LL), qw = min(siz[v[i]], o); p <= qw; ++ p){
                        dp[u][o] = max(dp[u][o], dp[u][o-p] + dp[v[i]][p] + w[i]*(1LL*(n-siz[v[i]])*siz[v[i]]+2LL*(k-p)*p-1LL*(n-siz[v[i]])*p-1LL*siz[v[i]]*(k-p)));
                    }
                siz[u] += siz[v[i]];
            }
    }
}

signed main()
{
    read(n); read(k);
    for (int i = 1; i < n; ++ i){
        int x, y, z;
        read(x); read(y); read(z);
        add(x, y, z); add(y, x, z);
    }
    Solver1::dfs(1, 0);
    printf("%lld\n", dp[1][k]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值