当前位置: 首页 > news >正文

树上dp之换根dp

基本概念:

换根dp是树上dp的一种

我们在什么时候需要用到换根dp呢?

当题目询问的属性,是需要当前结点为根时的属性,这个时候,我们就要使用换根dp

换根dp的基本思路:

假设题目询问的的属性为x

通常我们会进行两次dfs

第一次dfs,我们选取任意一个结点作为给出的无根树的根,对其进行dfs,并求出这个根的x,以及一些其他辅助数组(即节点与其子树的一些属性关系)

第二次dfs,我们记dp[i]为对于结点i而言,节点i作为树的根时,我们要求的属性x

那么,令当前结点为u,其子节点为v,我们需要对推到出dp[u]转移到dp[v]的转移方程,从而成功求出结点v作为根时的属性x,如此递归下去,直到将所有结点的dp值都求出来,我们就可以求得题目的询问答案了

例题1:

题目链接:P3478 [POI2008] STA-Station - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目大意:

给出一棵有n个节点的树,求出这样的一个节点,使得以这个节点为根时,所有节点的深度之和最大

题目分析:由于题目很直白地讲了是要求以节点为根时的属性,那么当然是采用换根dp的方法

由于是深度之间的转移,我们设数组s[i],表示的是以节点i为根的子树的深度之和

我们先选取1号节点为根,进行第一次dfs,预处理出s数组的值以及dp[1]的值

之后我们需要用第二次dfs进行关系转移

我们考察当前节点u以及其子结点v

当根从u转移到v时,以v为根的子树的每个结点的深度都减小了1,也就是总深度减小了s[v]

而对于以u为根的,且排除了v结点的子树而言,他们每个结点的深度都增大了1,也就是总深度增大了n-s[v]

由此,我们可以推到出,当根由u转到v时,总深度从dp[u]变到dp[v]的变化是:增加了n-2*s[v]

即得转移方程:dp[v]=dp[u]+n-2*s[v]

至此,我们只需要用第二次dfs求出每个节点的dp值,最后再从1到n遍历出最大的dp值,其对应的节点编号即为询问答案

实现代码:

#include <cstdio>
#include <iostream>
using namespace std;
// 换根dp
// 用s[i]表示以i为根时其子树的结点个数
// 令u为当前结点
// v为当前结点的子结点
// 则有s[u]=s[v]+1
// 第一次dfs,选取任意一个根,来预处理出所有的s[i]//换根的状态转移:
//dp[i]表示以i为根时,所有结点的深度之和
//令当前根为u,v为其子结点
//则将根由u转到v时
//所有在v的子树上的结点深度都会减少1,那么总深度就减少了s[v]
//所有不在v的子树上的结点的深度都会增加1,那么总深度就增加了n-s[v]
//由此,我们得知,dp[v]=dp[u]+n-2*s[v],此为状态转移方程
//我们第二次dfs来进行这个操作//最后只需遍历dp[i],找到最大的即可
const int N=1E6+10,M=N<<1;
int n;
//链式前向星加边
int to[M],nxt[M],h[N],tot;
void add(int a,int b){to[++tot]=b;nxt[tot]=h[a];h[a]=tot;
}
unsigned long long s[N],dp[N],dep[N];
void dfs1(int f,int u){dep[u]=dep[f]+1;s[u]++;for(int i=h[u],v;v=to[i];i=nxt[i]){if(v==f) continue;dfs1(u,v);s[u]+=s[v];}
}
void dfs2(int f,int u){for(int i=h[u],v;v=to[i];i=nxt[i]){if(v==f) continue;dp[v]=dp[u]+n-2*s[v];dfs2(u,v);}
}
int main(){cin>>n;for(int i=1,a,b;i<n;i++){cin>>a>>b;add(a,b);add(b,a);}dep[0]=-1;dfs1(0,1);for(int i=1;i<=n;i++) dp[1]+=dep[i];dfs2(0,1);unsigned long long max=0;int ans;for(int i=1;i<=n;i++)if(dp[i]>max){max=dp[i];ans=i;}cout<<ans;return 0;
}

例题2:

题目链接:3585 -- 积累度 --- 3585 -- Accumulation Degree (poj.org)

题目大意:定义A[i],表示的是结点i所能流到所有叶子结点的最大流量之和。其中由一个结点流向另一个节点的流量不能大于连接这两个结点的边的权值。

题目分析:

由于流量是由父结点流向子结点,这使得dfs不好处理(不知道一开始要流多少,有可能等于边权,也有可能小于边权)

所以我们选择把流量从子结点向父结点转移,也就是说,我们认为一个子结点v需要的流量为x,那么回去找它的父结点u,如果连接u与v的边权w大于等于x,那么父结点u所需要的流量就增加x;否则,父结点所需要的流量只能增加w(因为它最多只能流w到子结点v上)

由此,我们设出数组s1[i],表示的是i结点所需要的流量大小,由上述可以写出s1数组的转移方程,令当前结点为u,其子结点为v,那么s1[u]=s1[v]>w?w:s1[v]

又由于叶子结点v没有子结点了,那么它所需要的流量就应该初始化为连结到v的边的权值,因为它最多就只能要那么多。

怎么找到叶子结点呢?只要注意到其入度为1就好了

这样,我们就可以选取一个结点作为根进行第一次dfs,预处理出s1数组

这里注意不能选取叶子结点作为第一次dfs的根,因为会丢失掉其s1的值

第二次dfs:

设dp[u],其意义就是A[u],那么我们就需要推导其转移方程

考察当前根结点u转移到其子节点v

dp[u]表示u结点所需要的流量,那么这部分流量可以被分为两部分

一部分是v结点所转移到u的流量,我们记为Q1

另一部分是除了v节点外,其他子节点所需要的流量,我们记为Q2,则有Q2=dp[u]-Q1

考察v结点转移到u的流量:

当v结点所需的流量s1[v]>=w时,(w为边权),v只能转移w给u,此时Q1=w

而当s1[v]<w时,v能转移s1[v]给u,此时Q1=s1[v]

当根由u转到v时,Q2这部分流量就需要转到v上,由于边权w的限制,Q2的转移量可能会发生变化,我们设其转移量为Q2'

当Q2>=w时,只能转移w给v,此时Q2'=w

而当Q2<w时,就转移Q2给v,此时Q2'=dp[u]-Q1

而对于节点v而言,当其成为根后,其所需的流量,一部分来自于Q2的转移,另一部分来自于结点v自身所需的流量s1[v],也就是说,dp[v]=Q2'+s1[v]

至此,我们成功推导出了状态转移方程,只需进行第二次dfs,即可求出所有的dp值,进而求出最大值

细节问题:当v为叶子结点时,由于其所需的s1[v]来自于一开始的初始化,当它成为根后,其实并不需要s1[v]这一部分的贡献,因此,我们要将这个值减去

实现代码:

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2E5 + 10, M = N << 1;
int to[M], nxt[M], w[M], h[N], tot;//链式前向星
int in[N];//记录入度
int n, T;
//加边
void add(int a, int b, int c) {to[++tot] = b;w[tot] = c;nxt[tot] = h[a];h[a] = tot;in[b]++;
}long long s1[N], dp[N];
//第一次dfs获取s1数组
void dfs1(int f, int u) {for (int i = h[u], v; v = to[i]; i = nxt[i]) {if (f == v) continue;//当v为叶子结点时,dfs其之前,先初始化s1[v]的值为边权if (in[v] == 1) s1[v] = w[i];dfs1(u, v);//dfs完后,子结点向父结点转移s1的值s1[u] += s1[v] <= w[i] ? s1[v] : w[i];}
}
//第二次dfs
void dfs2(int f, int u) {for (int i = h[u], v; v = to[i]; i = nxt[i]) {if (f == v) continue;//当Q1=w[i]时,此时Q2=dp[u]-w[i]if (s1[v] >= w[i]) {//当Q2<w[i]时,此时Q2'=dp[u]-w[i]if (dp[u] - w[i] < w[i]) dp[v] = dp[u] - w[i] + s1[v];//否则Q2'=w[i]else dp[v] = w[i] + s1[v];//叶子结点减去重复贡献if (in[v] == 1) dp[v] -= s1[v];}//当Q1=s1[v]时,此时Q2=dp[u]-s1[v]else { //当Q2<w[i]时,此时Q2'=Q2if (dp[u] - s1[v] < w[i]) dp[v] = dp[u];//否则Q2'=w[i]else dp[v] = w[i] + s1[v];//叶子结点减去重复贡献if (in[v] == 1) dp[v] -= s1[v];}dfs2(u, v);}
}
int main()
{cin >> T;while (T--){//多组数据输入一定要记得初始化for (int i = 1; i <= tot; i++) to[i] = nxt[i] = w[i] = 0;for (int i = 1; i <= n; i++) in[i] = h[i] = s1[i] = dp[i] = 0;tot = 0;cin >> n;for (int i = 1, a, b, c; i < n; i++) {scanf("%d%d%d", &a, &b, &c);add(a, b, c); add(b, a, c);}int root = 1;//找到非叶子结点作为第一次dfs的根for (int i = 1; i <= n; i++)if (in[i] != 1) {root = i; break;}dfs1(0, root);dp[root] = s1[root];dfs2(0, root);long long ans = 0;//找最大值for (int i = 1; i <= n; i++) ans = max(ans, dp[i]);cout << ans << endl;}return 0;
}

相关文章:

树上dp之换根dp

基本概念&#xff1a; 换根dp是树上dp的一种 我们在什么时候需要用到换根dp呢&#xff1f; 当题目询问的属性&#xff0c;是需要当前结点为根时的属性&#xff0c;这个时候&#xff0c;我们就要使用换根dp 换根dp的基本思路&#xff1a; 假设题目询问的的属性为x 通常我们…...

2024/8/13 英语每日一段

Mackey says while Whole Foods has become more homogenized under Amazon, it did enable the store to do what it couldn’t have done independently. “People saw us as too expensive and out of touch with our customers,” he says. “The main thing Whole Foods n…...

Java多线程练习(1)

MultiProcessingExercise package MultiProcessingExercise120240813;public class MultiProcessingExercise {public static void main(String[] args) {/*需求&#xff1a;一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,请用多线程模拟卖票过程并打印…...

AI高级肖像动画神器LivePortrait

文章目录 前言一、安装1.1 源码安装1.2 windows一键启动包 二、人像生成2.1 浏览器2.2 输入图像2.3 选择驱动视频2.4 生成2.5 结果 三、动物生成3.1 浏览器3.2 输入图片3.3 选择视频3.4 生成3.5 最终结果 四、软件获取 前言 最近&#xff0c;快手可灵大模型团队、中国科学技术…...

Java反射机制深度解析与实践应用

Java反射机制深度解析与实践应用 引言 Java反射是Java语言提供的一种能力&#xff0c;允许程序在运行时访问、检测和修改其自身的属性和行为。反射机制是Java面向对象编程的一大亮点&#xff0c;也是Java框架和库常用的技术之一。 反射的基本概念 反射的核心是java.lang.re…...

Oracle递归查询层级及路径

一、建表及插入数据 ocation_idlocation_nameparent_location_id1广东省NULL2广州市13深圳市14天河区25番禺区26南山区37宝安区3 建表sql&#xff1a; CREATE TABLE locations (location_id NUMBER PRIMARY KEY,location_name VARCHAR2(100),parent_location_id NUMBER ); I…...

leetcode300. 最长递增子序列,动态规划附状态转移方程

leetcode300. 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2…...

C语言:字符串函数strcpy

该函数用于字符串的拷贝。 使用方法如下&#xff1a; #include<stdio.h> #include<string.h>int main() {char str[10];char* str1 "abcd";//strcpy(str, str1);//把str1复制到str&#xff0c;但此函数不安全所以用strcpy_sstrcpy_s(str, 10, str1);/…...

Day16-指针2

数组指针与指针数组 变量指针&#xff1a;指向变量的地址。 数组指针&#xff1a;指向数组的地址。 指针变量&#xff1a;存放其他变量地址的变量。 指针数组&#xff1a;存放数组元素指针的变量。 数组指针 概念&#xff1a;数组指针是指向数组的指针。特点&#xff1a; 先…...

数据结构(5.5_3)——并查集的进一步优化

Find操作的优化(压缩路径) 压缩路径——Find操作&#xff0c;先找到根节点&#xff0c;再将查找路径上所有结点都挂到根结点下 代码&#xff1a; //Find "查"操作优化&#xff0c;先找到根节点&#xff0c;再进行"路径压缩" int Find(int S[], int x) {…...

(回溯) LeetCode 131. 分割回文串

原题链接 一. 题目描述 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串。返回 s 所有可能的分割方案。 示例 1&#xff1a; 输入&#xff1a;s "aab" 输出&#xff1a;[["a","a","b"],[…...

【Linux】阻塞信号|信号原理|深入理解捕获信号|内核态|用户态|sigaction|可重入函数|volatile|SIGCHILD|万字详解

目录 ​编辑 一&#xff0c;常见的信号术语 二&#xff0c;信号在内核中的表示 信号标志位 Pending表 Block表 handler表 POSIX.1标准 三&#xff0c;sigset_t 信号集操作函数 sigemptyset sigfillset sigaddset sigdelset sigismember sigprocmask sig…...

基于Linux对 【进程地址空间】的详细讲解

研究背景&#xff1a; ● kernel 2.6.32 ● 32位平台 –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀– 在学习操作系统中想必大家肯定都见过下面这…...

[python]使用Pandas处理多个Excel文件并汇总数据

在数据分析和处理过程中&#xff0c;经常需要处理多个Excel文件&#xff0c;并将其中的数据进行汇总和分析。本文介绍使用Python的Pandas库来读取多个Excel文件&#xff0c;并汇总不同类型的数据&#xff0c;例如员工工资、工件数量等。 代码示例 以下是一个完整的代码示例&a…...

提升体验:UI设计的可用性原则

在中国&#xff0c;每年都有数十万设计专业毕业生涌入市场&#xff0c;但只有少数能够进入顶尖企业。尽管如此&#xff0c;所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时&#xff0c;我们可能会依赖直觉来决定设计方案&#xff0c;或者在寻找改善产品体验的切入…...

x264 编码器 SSIM 算法源码分析

SSIM SSIM(Structural Similarity Index)是一种用于衡量两幅图像之间视觉相似度的指标。它不仅考虑了图像的亮度、对比度和饱和度,还考虑了图像的结构信息。SSIM的值介于-1到1之间,值越接近1表示两幅图像越相似。 SSIM是基于以下三个方面来计算的: 亮度(Luminance):比…...

echarts使图表组件根据屏幕尺寸变更而重新渲染大小

效果图&#xff1a; 通过 window.addEventListener(resize, this.resizeChart); 实现 完整代码&#xff1a; <template><div class"dunBlock"><div class"char2" id"char2" ref"chart"></div></div…...

电脑图片损坏打不开怎么办?能修复吗?

照片和视频是记录和保存现实生活中的事件的最好方式。由于手机储存空间有限&#xff0c;一般我们会把有纪念意义的照片放到电脑上进行保存&#xff0c;但有时难免会遇到照片被损坏打不开的情况&#xff0c;一旦遇到这种情况&#xff0c;先不要急&#xff0c;也不要因为照片打不…...

vue-cli(二)

箭头函数 一般的函数&#xff1a; 这里window是用来调用函数的 function fun(){console.log(this) } window.fun(); 箭头函数&#xff1a; 1、如果只有一个参数&#xff0c;形参的小括号可以省略 2、如果只有一条语句&#xff0c;{}可以省略 完整的写法 let fun2 a>…...

今日头条的账号id在哪里看(网页版)

今日头条的账号id在哪里看&#xff08;网页版&#xff09; 1.https://mp.toutiao.com/profile_v4/index2.登录今日头条账号3.设置->头条号ID 1.https://mp.toutiao.com/profile_v4/index 2.登录今日头条账号 3.设置->头条号ID 打开下方链接&#xff1a; https://mp.to…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...