zoukankan      html  css  js  c++  java
  • hihoCoder #1063 缩地

    题目

    题意

    给定一棵带边权及点权的有根树 $T(V,E)$ ( $|V| le 100$ , 边权 $wcolon E o mathbb{N}^*$ , $w le 10^4$ , 点权 $v colon V o {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} o 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
    ", 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)$ . 树形背包的复杂度还需要研究.

  • 相关阅读:
    NodeJS学习笔记之Connect中间件应用实例
    NodeJS学习笔记之Connect中间件模块(二)
    NodeJS学习笔记之Connect中间件模块(一)
    前端构建工具gulpjs的使用介绍及技巧
    稳定的算法用于对象排序
    aspectJ
    SpringBoot自定义嵌入式Servlet容器
    一个对任务分而治之的java类ForkJoin详解
    VC6.0软件安装教程
    经典算法_杨辉三角,集合法
  • 原文地址:https://www.cnblogs.com/Patt/p/6357816.html
Copyright © 2011-2022 走看看