树上染色 - 树上背包

树上染色

思路

  • 一道树形dp题,想了好久,最终看了题解发现是突破口不对
  • 突破口:首先思考怎么求一条边被算过的次数,用组合数学方法思考可以想到就是这条边两个方向(定义一个左边,一个右边)的黑点个数的乘积 + 白点个数的乘积
  • 假设左边黑点的个数是x,且其左边节点个数是size, 树的总结点个数为n,则这条边计算次数有下面的公式
    t o t = x × ( k − x ) + ( s i z e − x ) × ( n − s i z e − k + x ) tot = x \times (k - x) + (size - x) \times (n - size - k + x) tot=x×(kx)+(sizex)×(nsizek+x)
  • 有了这个突破口就可以思考怎么dp了
  • 首先要明白每条边必定会对最终的答案做出贡献,固我们可以这样来定义状态: d p [ u ] [ i ] dp[u][i] dp[u][i]表示节点u的子树选i个黑点的最大贡献,由树形背包模型(对应博客点这里)可以得到方程,不难得出状态方程这样写
    d p [ u ] [ i ] = m a x { d p [ s o n ] [ j ] + d p [ u ] [ i − j ] + t o t × w } dp[u][i] = max\{ dp[son][j] + dp[u][i - j] + tot \times w \} dp[u][i]=max{dp[son][j]+dp[u][ij]+tot×w}
  • 值得注意的点: i − j i - j ij可能并没有这个状态,即可能这颗树在当前转移之前并没有 i − j i - j ij个节点,有一种解决方式就是将dp值初始化为-1,若在转移时dp值变了就证明他是合法的,否则并不合法
  • 但是这种转移经过讨论好像他的复杂度接近 O ( n 3 ) O(n^3) O(n3)
  • 还有一种的转移方式就是
    d p [ u ] [ i + j ] = m a x { d p [ s o n ] [ i ] + d p [ u ] [ j ] + t o t × w } dp[u][i + j] = max\{ dp[son][i] + dp[u][j] + tot \times w\} dp[u][i+j]=max{dp[son][i]+dp[u][j]+tot×w}
  • 这样转移的话洛谷是可以拿到100分的,这样就能保证转移时每个子树都只在他们的lca上计算一次,代码如下
int dfs(int u, int fa) {
    int cnt = 1;
    // dp[u][1] = dp[u][0] = 0;
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v ^ fa) {
            ll &w = edge[i].w; int son = dfs(v, u);
            for (int k = min(m, cnt); ~k; --k) { // 还是要反过来遍历
                for (int j = min(son, m); ~j; --j) {
                    if (j + k > m) continue;
                    ll tot = j * (m - j) + (son - j) * (n - son - m + j);
                    dp[u][j + k] = max(dp[u][j + k], dp[u][k] + dp[v][j] + tot * w);
                }
            }
            cnt += son;
        }
    }
    return cnt;
}
  • 注意到我第8行和之前的代码对应的循环都是反过来遍历的,原因也解释过了,这个是01背包中我们空间优化时利用到的转移方式用来防止转移覆盖的问题,当然还有一种不用担心转移覆盖的方式,贴上学长巨巨的代码,简单易懂
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 2000 + 5;
const int mod = 1e9 + 7;
ll dp[maxn][maxn];
ll sz[maxn] , n;
vector<pii> e[maxn];
ll tmp[maxn] , up;
void dfs (int u , int fa)
{
    sz[u] = 1;
    for (auto g : e[u]){
        int v = g.first;
        ll w = g.second;
        if (v == fa) continue;
        dfs(v , u);
        for (int i = 0 ; i <= sz[u] + sz[v]; i++) tmp[i] = dp[u][i];
        for (int i = 0 ; i <= min(sz[u] , up) ; i++){
            for (int j = 0 ; j <= min(sz[v] , up) ; j++){
                if (i + j > up) continue;
                ll val = 1ll * j * (up - j) /*黑点*/ + 1ll * (sz[v] - j) * (n - sz[v] - (up - j));
                val *= w;
                tmp[i + j] = max (tmp[i + j] , dp[u][i] + dp[v][j] + val);
            }
        }
        for (int i = 0 ; i <= sz[u] + sz[v] ; i++) dp[u][i] = tmp[i];
        sz[u] += sz[v];
    }
    return ;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> up;
    for (int i = 1 ; i < n ; i++){
        int x , y , z; cin >> x >> y >> z;
        e[x].pb (mp(y , z));
        e[y].pb (mp(x , z));
    }
    dfs (1 , 0);
    cout << dp[1][up] << endl;
    return 0;
}
  • 总结:这题是一个基础的树上背包问题,但是找到突破口很重要,不然可能怎么树上dp都不知道,解决方式:多刷题
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值