2023天梯赛L3-2 完美树

该问题是一个关于树的优化问题,使用动态规划dp[i][0/1/2]分别表示以节点i为根的子树中0、1节点数量相等、0多一个、0少一个的情况。在转移过程中,考虑子树的奇偶性和节点颜色,选择最优策略使得整个子树中黑白节点数量差不超过1,目标是最小化这个过程中的代价总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给定一颗树,每个节点颜色为0,或1,0代表黑色,1代表白色。每个点可以花费wiw_iwi的代价改变颜色,求将整棵树中黑色节点数量与白色节点数量相差不超过1的最小代价。

思路:

裸的树dpdpdp,我们定义:
dp[i][0]:dp[i][0]:dp[i][0]:iii为根的子树中0、1节点数量相等
dp[i][1]:dp[i][1]:dp[i][1]:iii为根的子树中0节点数量比1多一个
dp[i][2]:dp[i][2]:dp[i][2]:iii为根的子树中0节点数量比1少一个
显然,只有子树大小为偶数时dp[i][0]dp[i][0]dp[i][0]才有意义,为奇数时dp[i][1,2]才有意义dp[i][1,2]才有意义dp[i][1,2]才有意义
考虑转移:
sz[u]sz[u]sz[u]为以uuu为根的子树大小
对于当前点uuu,其子节点sonsonson,若sz[u]sz[u]sz[u]为偶数,那么直接将dp[son][0]dp[son][0]dp[son][0]转移。若为奇数,我们需要讨论。
假设有cntcntcnt个子树大小为奇数的sonsonson
cntcntcnt为奇数,那么说明sz[u]sz[u]sz[u]是偶数,我们只需考虑计算f[u][0]f[u][0]f[u][0]
color[u]=0color[u]=0color[u]=0,那么我们可以选择cnt2+1\frac{cnt}{2} + 12cnt+1f[son][1],cnt2个f[son][2]f[son][1],\frac{cnt}{2}个f[son][2]f[son][1],2cntf[son][2],这样加上uuu的颜色,0和1数量相等。
反之,若color[u]=1color[u]=1color[u]=1,那么我们可以选择cnt2+1\frac{cnt}{2} + 12cnt+1f[son][2],cnt2个f[son][1]f[son][2],\frac{cnt}{2}个f[son][1]f[son][2],2cntf[son][1].
cntcntcnt为偶数,则sz[u]sz[u]sz[u]为奇数,我们考虑转移f[u][1,2]f[u][1,2]f[u][1,2]
我们可以选择cnt2个f[son][1],cnt2个f[son][2]\frac{cnt}{2}个f[son][1],\frac{cnt}{2}个f[son][2]2cntf[son][1],2cntf[son][2],此时color[u]=0color[u]=0color[u]=0或者color[u]=1color[u]=1color[u]=1都可以。
或者选择cnt2−1\frac{cnt}{2}-12cnt1f[son][1]f[son][1]f[son][1],cnt2+1\frac{cnt}{2}+12cnt+1f[son][2]f[son][2]f[son][2],此时color[u]=0color[u]=0color[u]=0
或者选择cnt2−1\frac{cnt}{2}-12cnt1f[son][2]f[son][2]f[son][2],cnt2+1\frac{cnt}{2}+12cnt+1f[son][1]f[son][1]f[son][1],此时color[u]=1color[u]=1color[u]=1

还有一个问题是假如我们选了aaaf[son][1]f[son][1]f[son][1], cnt−acnt-acntaf[son][2]f[son][2]f[son][2],如何选使总代价最小。
令总代价sum=a∗f[son][1]+(cnt−a)∗f[son][2]=a∗(f[son][1]−f[son][2])+cnt∗f[son][2] \begin{aligned} sum &= a*f[son][1]+(cnt-a)*f[son][2] \\ &=a*(f[son][1]-f[son][2])+cnt*f[son][2] \end{aligned} sum=af[son][1]+(cnta)f[son][2]=a(f[son][1]f[son][2])+cntf[son][2]
那么我们根据选择f[son][1]f[son][1]f[son][1]的个数维护前几个f[son][1]−f[son][2]f[son][1]-f[son][2]f[son][1]f[son][2]即可,可以看代码如何实现。

const int N = 1e5 + 50;
const int M = N << 1;
const int mod = 1e9 + 7;

int h[N], e[M], ne[M], idx;
int n, color[N], w[N], fa[N];
int f[N][3], sz[N];

void add(int a, int b) {
    e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
void dfs(int u) {
    sz[u] = 1;
    int cnt = 0, sum0 = 0, sum2 = 0;
    vector<int> q;
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        dfs(j);
        sz[u] += sz[j];
        if(sz[j] & 1) {
            cnt++;
            sum2 += f[j][2];
            q.push_back(f[j][1] - f[j][2]);
        } else {
            sum0 += f[j][0];
        }
    }
    sort(q.begin(), q.end());
    int len = q.size();
    if(cnt == 0) { 
        f[u][1] = sum0 + (color[u] == 0 ? 0 : w[u]);
        f[u][2] = sum0 + (color[u] == 1 ? 0 : w[u]);
    } else if(cnt & 1) {
        int now = 0;
        for(int i = 0; i < len / 2; i++) now += q[i];
        if(color[u] == 0) f[u][0] = sum0 + sum2 + min(now, now + w[u] + q[len / 2]);
        else f[u][0] = sum0 + sum2 + min(now + q[len / 2], now + w[u]);
    } else {
        int now = 0;
        for(int i = 0; i < len / 2; i++) now += q[i];
        if(color[u] == 1) {
            f[u][1] = min(now + w[u], now + q[len / 2]) + sum0 + sum2;
            f[u][2] = min(now, w[u] + now - q[len / 2 - 1]) + sum0 + sum2;
        } else {
            f[u][1] = min(now, now + q[len / 2] + w[u]) + sum0 + sum2;
            f[u][2] = min(now + w[u], now - q[len / 2 - 1]) + sum0 + sum2;
        }
    }
}

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0); 

    memset(h, -1, sizeof h);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        int c, p, k;
        cin >> c >> p >> k;
        color[i] = c;
        w[i] = p;
        while(k--) {
            int x; cin >> x;
            add(i, x);
            fa[x] = i;
        }
    }
    int root = 1;
    while(root <= n && fa[root]) ++root;
    dfs(root);
    if(n & 1) cout << min(f[root][1], f[root][2]);
    else cout << f[root][0];

    return 0;
}
/*
f[i][0]:0=1
f[i][1]:0>1
f[i][2]:0<1
*/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值