详解树形DP

前言

给定一颗有N个节点的树(一般是无根树,就有N-1条无向边),可以任选一个节点作为根节点

一般以节点从深到浅(子树从小到大)的顺序作为dp阶段顺序

dp的状态表示中,第一维通常是节点编号(节点编号代表了以该节点为根的子树)

对于每个节点x,先递归在它的每个子节点上进行dp,回溯时,从子节点向x进行状态转移

A - Anniversary part

N个员工,编号为1~N

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直属上司。现在有个宴会,宴会每邀请来一个员工 i 都会增加一定的快乐指数 Ri,但如果某个员工的直属上司来了,那么这个员工就不会来。计算邀请哪些员工可以使快乐指数最大,输出最大的快乐指数

设 dp [x] [0] 表示在 x 为根的子树中邀请部分员工,并且 x 不参加时,快乐指数总和的最大值,此时 x 子节点(直接下属)可以参加也可以不参加 (s表示x子节点)

dp [x] [1] 表示在 x 为根的子树中邀请部分员工,并且 x 参加时,快乐指数总和的最大值,此时 x 子节点(直接下属)都不能参加,H[x] 表示当前节点(x)的快乐指数 (s表示x子节点)

这个题给的是有根树,就可以从根节点开始,dp目标就是 max(F[root,0] , F[root,1]) 时间复杂度ON

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

vector<int> son[10010];
int dp[10010][2];//0不参加,1参加
int v[10010];//记录有没有父节点
int h[10010];//记录快乐指数
int n;

void DFS(int x){
    dp[x][0] = 0;
    dp[x][1] = h[x];
    for (int i = 0; i < son[x].size(); i++)
    {
        int y = son[x][i];
        DP(y);
        dp[x][0] += max(dp[y][0],dp[y][1]);
        dp[x][1] += dp[y][0];
    }
}

int main(){
    cin>>n;
    for (int i = 1; i <=n ; i++)
        scanf("%d",&h[i]);
    for (int i = 1; i <n ; i++)
    {
        int x,y;
        cin>>x>>y;
        v[x] = 1;//x有爸爸
        son[y].push_back(x);//x是y的子节点
    }
    int root;
    for (int i = 1; i <= n; i++)
        if(!v[i]){ //i没有爸爸
            root = i;
            break;
        }
    DFS(root);
    cout << max(dp[root][0],dp[root][1]) << endl;
    return 0;
}

B - Strategic game

有n个点,在某些点上放置哨兵,每个哨兵可以监控和它有边相连的点,问监视所有的点需要的最少的哨兵数

也就是说一颗n个结点的树,要求选出其中的一些顶点,使得对于树中的每条边(u, v),u和v至少有一个被选中,要求给出选中顶点数最少的方案

设 dp [x] [0] 表示在不选择节点 x 的情况下,以 x 为根节点的子树,最少需要选择的节点数

​当i为叶子节点时

​当i不为叶子节点时 (s表示x子节点)

dp [x] [1] 表示在选择节点 x 的情况下,以 x 为根节点的子树,最少需要选择的节点数
​当i为叶子节点时

​当i不为叶子节点时 (s表示x子节点)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define maxn 1508
using namespace std;

int dp[maxn][2];
int soncnt[maxn];
int parent[maxn];
int n;

void DFS(int x) {
    int i, d1=0, d0=0;
    if (soncnt[x] == 0) {
        dp[x][0] = 0;
        dp[x][1] = 1;
        return;
    }
    for (i=0; i < n; i++) {
        if (parent[i] == x) {
            DFS(i);
            d1 += min(dp[i][0], dp[i][1]);
            d0 += dp[i][1];
        }
    }
    dp[x][1] = d1 + 1;
    dp[x][0] = d0;
}

int main() {
    int dad, son, m;
    while (cin >> n) {
        memset(soncnt, 0, sizeof(soncnt));
        memset(parent, -1, sizeof(parent));
        int root = -1;
        for (int i = 0; i < n; i++) {
            scanf("%d:(%d)", &dad, &m);
            soncnt[dad] = m;
            if (root == -1) {
                root = dad;
            }
            while (m--) {
                scanf("%d", &son);
                parent[son] = dad;
            }
        }
        DFS(root);
        cout << min(dp[root][0], dp[root][1]) << endl;
    }
    return 0;
}

C - Tree Cutting

给一颗n个结点的树,节点编号为1~n

问:删除哪些结点后,剩余各个子树的大小均小于原总结点数的一半

拆除一个节点后,剩余部分为其若干儿子的子树以及该节点上层所连其余部分(n-size[i]),只要这些连接块大小都不超过n/2,该节点就满足条件。因而我们可以先求出每个节点所管辖的那棵树的大小,自下而上地为每个节点求出其子树规模(该点规模=其儿子的规模和+1)。

在建树的时候可以直接用连接表(vector)储存无向边,这时由于无法区分与每个点相连的是其父节点还是子节点,会引发问题:在dfs的时候把父节点误认作儿子节点,解决方法就是在递归的时候传入父节点编号,然后在递归,计算规模,比较大小的时候避开它就可以了

#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;

int n;
int root;
vector<int>G[10000+400];
vector<int>ans;
int sz[10000+400];

void dfs(int par,int u){
    sz[u]=1;
    for(int i=0;i<(int)G[u].size();i++){
        if(G[u][i]!=par)
            dfs(u,G[u][i]);
    }
    int piece=0;
    for(int i=0;i<(int)G[u].size();i++)
        if(par!=G[u][i]){
            sz[u]+=sz[G[u][i]];
            piece=max(piece,sz[G[u][i]]);
        }
    piece=max(piece,n-sz[u]);
    if(piece<=n/2)
        ans.push_back(u);
}

int main(){
    while(cin>>n){
        memset(sz,0,sizeof(sz));
        int x,y;
        for(int i=0;i<n-1;i++){
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
        }
        dfs(0,1);
        sort(ans.begin(),ans.end());
        for(int i=0;i<(int)ans.size();i++)
            cout<<ans[i]<<endl;
    }
    return 0;
}

LCA 最近公共祖先

倍增(基于二分的方法)

假如树上的两个点,处于同一深度时,对于往上跳mid步,显然有单调性:

如果它们往上跳mid步是同一个点,则这个点是它们的共同祖先,但不一定是最近公共祖先

那么如果我们可以快速得到每个点往上跳若干步是谁,显然我们可以利用二分来求LCA

这个可以用倍增来做。

倍增的思想其实非常简单,就是利用二进制的思想。一个显然的结论有:

节点u往上跳2^(k+1)的布的祖先 = (节点u往上跳2k布的祖先)往上跳2k布的祖先

用代码表示就是:

fa[u][k + 1] = fa[ fa[u][k] ][k];

这个的预处理也非常简单,我们只需要初始化每个fa[u][0]就可以了就可以了,因为接下来的部分都可以根据上面的公式递推。

然后预处理好这个之后,我们就可以根据这个进行二分了,代码如下:

int fa[maxn][20], dep[maxn];

void dfs(int u, int f, int d)
{
    dep[u] = d;
    fa[u][0] = f;
    for(int i = 1; i < 20; ++i)
    {
        fa[u][i] = fa[fa[u][i -  1]][i] - 1;
    }
    for(int i = head[u]; ~i; i = nxt[i])
    {
        int t = to[i];
        if(t != f)
        {
            dfs(t, u, d + 1);
        }
    }
}

int lca(int u, int v)
{
    if(dep[u] < dep[v]) swap(u, v);
    int k = dep[u] - dep[v];
    for(int i = 0; i < k; ++i)
    {
        if((1 << i) & k) u = fa[u][i];
    }
    if(u == v) return u;
    for(int i = 19; i >= 0; --i)
    {
        if(fa[u][i] != fa[v][i])
        {
            u = fa[u][i];
            v = fa[v][i];
        }
    }
    return fa[u][0];
}

以上就是详解树形DP的详细内容,更多关于树形DP的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#使用动态规划解决0-1背包问题实例分析

    本文实例讲述了C#使用动态规划解决0-1背包问题的方法.分享给大家供大家参考.具体如下: // 利用动态规划解决0-1背包问题 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Knapsack_problem // 背包问题关键在于计算不超过背包的总容量的最大价值 { class Program { static void Main() { int i;

  • C++动态规划之最长公子序列实例

    本文实例讲述了C++动态规划之最长公子序列解决方法.分享给大家供大家参考.具体分析如下: 问题描述: 求出两个字符串中的最长公子序列的长度. 输入: csblog belong 输出: max length = 4 实现代码: #include <stdio.h> #include <string.h> int arr[200][200]; /* 表示str1的前i位和str2的前j位的最长公子序列的长度 */ int main() { char str1[100],str2[10

  • java背包问题动态规划算法分析

    背包问题 [题目描述] 一个旅行者有一个最多能装 MM 公斤的背包,现在有 nn 件物品,它们的重量分别是W1,W2,-,WnW1,W2,-,Wn,它们的价值分别为C1,C2,-,CnC1,C2,-,Cn,求旅行者能获得最大总价值. [输入] 第一行:两个整数,MM(背包容量,M<=200M<=200)和NN(物品数量,N<=30N<=30): 第2-N+12-N+1行:每行二个整数Wi,CiWi,Ci,表示每个物品的重量和价值. [输出] 仅一行,一个数,表示最大总价值. [输入

  • 动态规划之矩阵连乘问题Python实现方法

    本文实例讲述了动态规划之矩阵连乘问题Python实现方法.分享给大家供大家参考,具体如下: 给定n个矩阵{A1,A2,-,An},其中Ai与Ai+1是可乘的,i=1,2 ,-,n-1.如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少. 例如: A1={30x35} ; A2={35x15} ;A3={15x5} ;A4={5x10} ;A5={10x20} ;A6={20x25} ; 结果为:((A1(A2A3))((A4A5)A6))  最小的乘次为15125.

  • C++动态规划之背包问题解决方法

    本文实例讲述了C++动态规划之背包问题解决方法.分享给大家供大家参考.具体分析如下: 问题描述: 背包的最大容量为W,有N件物品,每件物品重量为w,价值为p,怎样选择物品能使得背包里的物品价值最大? 输入: 10 3   (W,N) 4 5   (w,p) 6 7   (w,p) 8 9   (w,p) 实现代码: #include <stdio.h> #define THING 20 #define WEIGHT 100 int arr[THING][WEIGHT]; /* 背包容量为wei

  • JavaScript程序设计高级算法之动态规划实例分析

    本文实例讲述了JavaScript程序设计高级算法之动态规划.分享给大家供大家参考,具体如下: 主要是看了<数据结构与算法>有所感悟,虽然这本书被挺多人诟病的,说这有漏洞那有漏洞,但并不妨碍我们从中学习知识. 其实像在我们前端的开发中,用到的高级算法并不多,大部分情况if语句,for语句,swith语句等等,就可以解决了.稍微复杂的,可能会想到用递归去的解决. 但要注意的是递归写起来简洁,但实际上执行的效率并不高. 我们再看看动态规划的算法: 动态规划解决方案从底部开始解决问题, 将所有小问题

  • 浅析python实现动态规划背包问题

    一个包可以背4kg的东西,现在有四件东西,重量分别为1kg,4kg,3kg,1kg,价值为:1500,3000,2000,2000: 现在要求你,在包里背的东西价值最大,但是不能超过背包的最大载重量 #几件物品的重量 w = [0,1,4,3,1] #几件物品的价值 v= [0, 1500, 3000, 2000, 2000] #物品数量 n = len(w) - 1 #包的载重量 m = 4 #建立一个列表表示在包中的物品,元素是True时代表对应元素放入 x = [] #放入包中的总价值 v

  • C语言动态规划之背包问题详解

    01背包问题 给定n种物品,和一个容量为C的背包,物品i的重量是w[i],其价值为v[i].问如何选择装入背包的物品,使得装入背包中的总价值最大?(面对每个武平,只能有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入物品多次) 声明一个数组f[n][c]的二维数组,f[i][j]表示在面对第i件物品,且背包容量为j时所能获得的最大价值. 根据题目要求进行打表查找相关的边界和规律 根据打表列写相关的状态转移方程 用程序实现状态转移方程 真题演练: 一个旅行者有一个最多能装M公斤的背

  • Java矩阵连乘问题(动态规划)算法实例分析

    本文实例讲述了Java矩阵连乘问题(动态规划)算法.分享给大家供大家参考,具体如下: 问题描述:给定n个矩阵:A1,A2,...,An,其中Ai与Ai+1是可乘的,i=1,2...,n-1.确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少.输入数据为矩阵个数和每个矩阵规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数. 问题解析:由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序.这种计算次序可以用加括号的方式来确定.若一个矩阵连乘积的计算次序完全确

  • 详解树形DP

    前言 给定一颗有N个节点的树(一般是无根树,就有N-1条无向边),可以任选一个节点作为根节点 一般以节点从深到浅(子树从小到大)的顺序作为dp阶段顺序 dp的状态表示中,第一维通常是节点编号(节点编号代表了以该节点为根的子树) 对于每个节点x,先递归在它的每个子节点上进行dp,回溯时,从子节点向x进行状态转移 A - Anniversary part N个员工,编号为1~N 他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直属上司.现在有个宴会,宴会每邀请来一个员

  • SQL Server 树形表非循环递归查询的实例详解

    很多人可能想要查询整个树形表关联的内容都会通过循环递归来查...事实上在微软在SQL2005或以上版本就能用别的语法进行查询,下面是示例. --通过子节点查询父节点 WITH TREE AS( SELECT * FROM Areas WHERE id = 6 -- 要查询的子 id UNION ALL SELECT Areas.* FROM Areas, TREE WHERE TREE.PId = Areas.Id ) SELECT Area FROM TREE --通过父节点查询子节点 WIT

  • 详解js树形控件—zTree使用总结

    0 zTree简介 树形控件的使用是应用开发过程中必不可少的.zTree 是一个依靠 jQuery 实现的多功能 "树插件".优异的性能.灵活的配置.多种功能的组合是 zTree 最大优点. 0.0 zTree的特点 最新版的zTree将核心代码按照功能进行了分割,不需要的代码可以不用加载,如普通使用只需要加载核心的jquery.ztree.core-3.5.js,需要使用勾选功能加载jquery.ztree.excheck-3.5.min.js,需要使用编辑功能加载jquery.zt

  • BootStrap实现树形目录组件代码详解

    需求描述 产品添加页面,需要选择车型.在bootStrap的modal上弹出子modal来使用. 车型一共有4级目录.要使用目录树. 然后分活动和商品两种,需要能够通过不通参数来调用该组件. 车型品牌要使用字母导航. 技术实现 数据都是后端传json过来,我们ajax获取然后操作. 由于车型总数据有几万条以上,不可能一次性请求过来.这里我们使用异步的方式,每点击一次目录节点,加载它的下一级. 这里我们用两个参数来控制活动和商品的不同加载._showPrice和opened 后端传过来的第一级数据

  • jquery树形插件zTree高级使用详解

    使用高级zTree进行对属性结构进行操作的时候,做好的方式是参考官网的API文档. 本文简单介绍下如何通过后台传递过来一个树形结构的树,并且通过页面进行加载. [与后台交互步骤]1.编写页面,引入zTree相关插件:2.编写js脚本,设定树形结构的基本属性:3.编写zTree的PO对象:4.编写返回属性结构的方法(json格式返回):5.页面请求树. 1.引入zTree相关的插件: <script type="text/javascript" src="<%=re

  • 基于Vue实现可以拖拽的树形表格实例详解

    因业务需求,需要一个树形表格,并且支持拖拽排序,任意未知插入,github搜了下,真不到合适的,大部分树形表格都没有拖拽功能,所以决定自己实现一个.这里分享一下实现过程,项目源代码请看github,插件已打包封装好,发布到npm上 本博文会分为两部分,第一部分为使用方式,第二部分为实现方式 安装方式 npm i drag-tree-table --save-dev 使用方式 import dragTreeTable from 'drag-tree-table'  模版写法 <dragTreeTa

  • 详解DAG上的DP

    DAG即有向无环图 这里举出两经典的DAG模型,嵌套矩形和硬币问题 嵌套矩形(不固定起点最长路及其字典序) 描述 有n个矩形,每个矩形可以用 (a,b) 来描述,表示长和宽 矩形 X(a,b) 可以嵌套在矩形 Y(c,d) 中,当且仅当 a<c,b<d 或者 b<c,a<d(旋转90度) 例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中 你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内 分析 矩形间的"可嵌套&quo

  • MyBatis实现两种查询树形数据的方法详解(嵌套结果集和递归查询)

    目录 方法一:使用嵌套结果集实现 1,准备工作 2,实现代码 方法二:使用递归查询实现 树形结构数据在开发中十分常见,比如:菜单数.组织树, 利用 MyBatis 提供嵌套查询功能可以很方便地实现这个功能需求.而其具体地实现方法又有两种,下面分别通过样例进行演示. 方法一:使用嵌套结果集实现 1,准备工作 (1)假设我们有如下一张菜单表 menu,其中子菜单通过 parendId 与父菜单的 id 进行关联: (2)对应的实体类如下: @Setter @Getter public class M

  • C++数位DP复杂度统计数字问题示例详解

    目录 一.问题描述: 二.问题分析: 1. 抽取题意: 2. 初步思考: 3. 示例分析: 4. 总结规律: 5. 解除约定: 三. 编写代码: 四. 相关例题: Tips:如果你是真的不理解,不要只看,拿出笔来跟着步骤自己分析. 一.问题描述: 一本书的页码从自然数 1 开始顺序编码直到自然数 n .书的页码按照通常的习惯编排, 每个页码不含多余的前导数字 0. 例如, 第 6 页用数字 6 表示而不是 06 或 006等. 数字计数问题要求对给定书的总页码 n,计算书的全部页码分别用到多少次

  • C语言数学问题与简单DP背包问题详解

    目录 数学 买不到的数目 蚂蚁感冒 饮料换购 简单DP 背包问题 二维 一维 数学 顾名思义,数学类的题就是都可以用数学知识求解. 买不到的数目 这是第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAC组的一道题 小明开了一家糖果店. 他别出心裁:把水果糖包成4颗一包和7颗一包的两种. 糖果不能拆包卖. 小朋友来买糖的时候,他就用这两种包装来组合. 当然有些糖果数目是无法组合出来的,比如要买 10 颗糖. 你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17. 大于17的任何数字

随机推荐