• [HAOI2015]树上染色


    首先一个简单的想法是令 \(dp_{i, j}\) 为以 \(i\) 为根的子树内选择了 \(j\) 个黑点的最大收益,但你会发现这个东西无论怎样转移都需要涉及深度和,如果我们记入深度和就不可能通过这道题了。

    不妨换一种想法,我们把每条路径的贡献拆分到每条边上去,即我们计算每条边对总答案的贡献。不难发现我们只需要知道一边有多少个黑色的点即可,于是我们可以令 \(dp_{i, j}\) 为以 \(i\) 为根的子树内黑点有 \(j\)\(i\) 子树内所有边的最大贡献。可以发现这样就很好转移了,需要注意一下我们树形背包枚举第二层如果需要枚举 \(k = 0\),那么我们需要提前转移掉,或者顺序转移,因为如果我们先转移出了 \(dp_{i, j}\) 后面又使用 \(dp_{i, j + 0}\) 转移 \(dp_{i, j}\) 就会重复计算贡献。

    然而上面这个做法被链卡掉了,标准的树形 \(dp\) 枚举方式其实是这样的,对于每个子树 \(v_i\),第一层我们枚举到 \(\sum\limits_{j = 1} ^ {i - 1} s_{v_j}\),第二层一样枚举到 \(s_{v_i}\),这样复杂度就对了。因为 \(\sum\limits \sum\limits s_{v_i} \times \sum\limits_{j = 1} ^ {i - 1} s_{v_j} = \sum\limits \sum\limits \sum\limits s_{v_i} \times s_{v_j}(v_i \ne v_j)\) 相当于树上点对数量,这当然是 \(n ^ 2\) 级别的。另外,在这种情况时我们的 \(k\) 要倒序枚举,因为如果我们先考虑 \(k = 0\) 那么接下来我们的 \(dp_{i, j}\) 都会被增加上 \(dp_{i, j + 0} + \cdots\) 的贡献,也会重复计算。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 2000 + 5
    #define int long long
    #define rep(i, l, r) for(int i = l; i <= r; ++i)
    #define dep(i, l, r) for(int i = r; i >= l; --i)
    #define Next(i, u) for(int i = h[u]; i; i = e[i].next)
    struct edge{
        int v, next, w;
    }e[N << 1];
    int n, u, v, w, T, tot, s[N], h[N], dp[N][N];
    int read(){
        char c; int x = 0, f = 1;
        c = getchar();
        while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    void add(int u, int v, int w){
        e[++tot].v = v, e[tot].w = w, e[tot].next = h[u], h[u] = tot;
        e[++tot].v = u, e[tot].w = w, e[tot].next = h[v], h[v] = tot;
    }
    void dfs(int u, int fa){
        s[u] = 1, dp[u][0] = dp[u][1] = 0;
        Next(i, u){
            int v = e[i].v; if(v == fa) continue;
            dfs(v, u);
            dep(j, 0, min(T, s[u])) dep(k, 0, min(T - j, s[v])) if(dp[v][k] != -1 && dp[u][j] != -1){
                int val = (T - k) * k * e[i].w + (s[v] - k) * (n - s[v] - T + k) * e[i].w;
                dp[u][j + k] = max(dp[u][j + k], dp[v][k] + dp[u][j] + val);
            }
            s[u] += s[v];
        }
    }
    signed main(){
        n = read(), T = read(), memset(dp, -1, sizeof(dp));
        rep(i, 1, n - 1) u = read(), v = read(), w = read(), add(u, v, w);
        dfs(1, 0);
        printf("%lld", dp[1][T]);
        return 0;
    }
    
  • 相关阅读:
    Hashcode的作用
    java 强弱软虚 四种引用,以及用到的场景
    Object类有哪些公用方法?
    equals和==的区别
    switch能否用string做参数
    Java九种基本数据类型,以及他们的封装类
    Singleton(Java)
    快速排序和二分查找(Javascript)
    快速排序和二分查找(Go)
    ubuntn 安装 MySQL
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13432113.html
Copyright © 2020-2023  润新知