Codeforces gym103389J 树形 DP

题意

传送门 Codeforces gym103389J 最大权边独立集

题解

从边的角度考虑,会涉及两个节点的枚举,不易处理。仅考虑新增的 j ( 0 ≤ j ≤ k , 2 × j ≤ n ) j(0\leq j\leq k,2\times j\leq n) j(0jk,2×jn) 条边出现在最大权边独立集的情况,问题等价于选择图中 2 × j 2\times j 2×j 个节点删除的最大边权独立集。

d p [ v ] [ i ] [ j ] dp[v][i][j] dp[v][i][j] 代表以节点 v v v 为根的子树上,删除 i i i 个节点, v v v 状态为 j j j 时,子树的最大边权独立集。其中 j = 0 j=0 j=0 代表 v v v 未被删除或不属于独立集上某条边的端点;反之亦然。

对于形如 d p [ v ] [ i + j ] = ∑ d p [ v ] [ i ] + d p [ u ] [ j ] dp[v][i+j] = \sum dp[v][i] + dp[u][j] dp[v][i+j]=dp[v][i]+dp[u][j] 的树形背包问题,背包容量取 min ⁡ ( s i z e v , k × 2 ) \min (size_v, k\times 2) min(sizev,k×2) 情况下(前者为子树节点数)时间复杂度 O ( n k ) O(nk) O(nk)

DP 时将根节点看作第一颗子树,实现会简洁一些。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int MAXN = 1e5 + 5, MAXK = 105;
int N, K, P;
struct edge
{
    int to, cost;
};
vector<edge> G[MAXN];
ll dp[MAXN][MAXK * 2][2], tmp[MAXK * 2][2];
int sz[MAXN];

void _max(ll &x, ll y) { x = max(x, y); }

void dfs(int v, int p)
{
    sz[v] = 1;
    for (int i = 0; i <= K * 2; ++i)
        dp[v][i][0] = dp[v][i][1] = -1;
    dp[v][0][0] = dp[v][1][1] = 0;
    for (auto &e : G[v])
        if (e.to != p)
        {
            int u = e.to;
            dfs(u, v);
            int a = min(sz[v], K * 2), b = min(sz[u], K * 2);
            sz[v] += sz[u];
            int c = min(sz[v], K * 2);
            for (int i = 0; i <= c; ++i)
                for (int j = 0; j < 2; ++j)
                    tmp[i][j] = -1;
            for (int i = a; i >= 0; --i)
                for (int j = 0; j <= b && j + i <= c; ++j)
                {
                    if (dp[v][i][0] != -1)
                    {
                        for (int k = 0; k < 2; ++k)
                            if (dp[u][j][k] != -1)
                                _max(tmp[i + j][0], dp[v][i][0] + dp[u][j][k]);
                    }
                    if (dp[v][i][1] != -1)
                    {
                        for (int k = 0; k < 2; ++k)
                            if (dp[u][j][k] != -1)
                                _max(tmp[i + j][1], dp[v][i][1] + dp[u][j][k]);
                    }
                    if (dp[v][i][0] != -1 && dp[u][j][0] != -1)
                        _max(tmp[i + j][1], dp[v][i][0] + dp[u][j][0] + e.cost);
                }
            for (int i = 0; i <= c; ++i)
                for (int j = 0; j < 2; ++j)
                    dp[v][i][j] = tmp[i][j];
        }
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> N >> K >> P;
    for (int i = 1; i < N; ++i)
    {
        int u, v, w;
        cin >> u >> v >> w;
        --u, --v;
        G[u].push_back({v, w});
        G[v].push_back({u, w});
    }
    dfs(0, -1);
    ll res = 0;
    for (int k = 0; k <= K && 2 * k <= N; k++)
    {
        if (dp[0][k * 2][0] != -1)
            _max(res, (ll)k * P + dp[0][k * 2][0]);
        if (dp[0][k * 2][1] != -1)
            _max(res, (ll)k * P + dp[0][k * 2][1]);
    }
    cout << res << '\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值