hihoCoder #1063 缩地

题目

题意

给定一棵带边权及点权的有根树 $T(V,E)$ ( $|V| \le 100$ , 边权 $w\colon E \to \mathbb{N}^*$ , $w \le 10^4$ , 点权 $v \colon V \to {0,1,2}$ ). 要求回答 $q$ ( $q \le 10^5$ ) 组询问, 询问格式: 给定 $d \in \mathbb{N} $ ( $d \le 10^6$ ), 问从根节点出发累计移动距离不超过 $d$ , 经过的节点权值之和的最大值 (重复经过的节点只算一次).

树形背包

将问题向树形背包转化.
为了描述方便, 令 $N=|V|$ , 将树的节点从 $1$ 到 $N$ 编号, 且令根节点编号为 $1$ .
DP状态:
$dp[i][j]$ : 在子树 $i$ 中移动, 距离不超过 $j$ 所能获得的最大收益.
复杂度 $O(Nd^2)$ (?) , 不能容忍.

注意到点权不超过2, 从而节点权值和为 $O(N)$ .
考虑新DP状态:
$dp[i][j]$ : 在子树 $i$ 中移动, 收益 恰好 为 $j$ 所需的最小移动距离.
复杂度为 $O(N^2)$ .

状态细化

上述两种DP状态是从一般背包问题出发得出的, 在本题中并不能直接转移, 需要细化 (增维); 两种DP状态的转移方式类似, 以第二种DP状态为例:
$dp[0][i][j]$ : 在子树 $i$ 中移动, 收益恰好为 $j$ 且最后回到节点 $i$ 所需的最小移动距离.
$dp[1][i][j]$ : 在子树 $i$ 中移动, 收益恰好为 $j$ , 不论最后在哪个节点, 所需的最小移动距离.

转移方程

将子树看做 泛化物品 合并即可.

泛化物品

(见 崔添翼《背包九讲》)
泛化物品可一般地表示为二元组
$(n, g)$ , $n \in \mathbb{N}$ , $g \colon {0, 1, 2, \dots, n} \to \mathbb{Z}$ , $g$ 是收益函数.
采用 启发式合并 的方法合并两泛化物品 $(n_1, g_1)$ , $(n_2, g_2)$ 的复杂度为 $O(n_1n_2)$ .

Implementation

#include <bits/stdc++.h>
using namespace std;

const int N=105;
vector<pair<int,int>> g[N];
const int M=205;
int dp[2][N][M], v[N], tmp[M];

int dfs(int u, int f){
    int sum=v[u];
    // boundary condition
    dp[0][u][v[u]]=dp[1][u][::v[u]]=0;
    for(auto e: g[u]){
        int v=e.first, w=e.second;
        if(v!=f){
            int sum_v=dfs(v, u);
            sum+=sum_v;
            for(int i=sum; i>=::v[u]+::v[v]; --i)
                for(int j=::v[v]; j<=min(i-::v[u], sum_v); j++){
                    dp[0][u][i]=min(dp[0][u][i], dp[0][u][i-j]+dp[0][v][j]+2*w);
                    dp[1][u][i]=min(dp[1][u][i], dp[1][u][i-j]+dp[0][v][j]+2*w);
                    dp[1][u][i]=min(dp[1][u][i], dp[0][u][i-j]+dp[1][v][j]+w);
                }
        }
    }
    return sum;
}



int main(){
    int n;
    scanf("%d", &n);
    for(int i=1; i<=n; i++)
        scanf("%d", v+i);
    memset(dp, 0x3f, sizeof(dp));
    //boundary conditon
    // for(int i=1; i<=n; i++) // This is not the boundary!
    //     dp[0][i][0]=0;

    for(int i=1; i<n; i++){
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    int sum=dfs(1, 1);
    int q;
    scanf("%d", &q);
    for(; q--; ){
        int d;
        scanf("%d", &d);
        for(int i=sum; i>=0; i--)
            if(dp[1][1][i]<=d){
                printf("%d\n", i);
                break;
            }
    }
    return 0;
}

Reference

本文参考了解题报告1解题报告2.
对比这两篇解题报告可更好地理解:

  1. DP的基本原理与概念
  2. 泛化物品的合并

其他

关于树形背包问题的复杂度, 我感觉应该能给出一个更紧的复杂度, 仍需研究.
2015年国家集训队张恒捷的论文《DP的一些优化技巧》$\S$2.7 启发式合并DP数组 中讨论了这个问题.

将一个长为 $x$ 的数组看做一个权重为 $x$ 的点, 所有点权重之和为 $m$ . 合并权重为 $x$ 与 $y$ 的点需要花费 $O(T(x,y))$ 的复杂度, 得到一个权重为 $x+y$ 的点.

$T(x,y) =xy$

这种情况多数在树上问题出现. 无论按什么顺序合并, 总复杂度都是 $O(m^2)$ 而非 $O(m^3)$ . 关于这一点, 我们可以把合成的点向合成它的点连边, 整个结构就是一棵树, $T(x,y)$ 相当于枚举左右子树内所有点的匹 (分?) 配方案. 这时只要注意到每一对点只会在他们 LCA 处枚举到就行了.
所以该情况的复杂度为 $O(m^2)$ .

不难分析出这道题的复杂度为 $O(N^2)$ .
需要特别注意的是, 这个题目不是一个典型的背包问题, 而是一个树上的合并问题. 在背包问题中, 合并两泛化物品受制于背包容量 $C$ , 复杂度不超过 $O(C^2)$ . 树形背包的复杂度还需要研究.

转载于:https://www.cnblogs.com/Patt/p/6357816.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值