当前位置: 首页 > 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…...

单体应用提高性能和高并发处理-合理使用多核处理

合理使用多核处理能力是提升单体应用性能和处理高并发能力的重要手段。以下是关于如何合理利用多核处理器的详细讲解&#xff0c;包括多线程编程、线程池的使用、并行计算、以及如何避免常见的性能陷阱。 1. 多线程编程 多线程编程是利用多核处理器的直接方式。每个线程可以在…...

基于STM32/GD32的双CAN、一路485开发板

双CAN开发板 双CAN、一路485开发板的设计开发板配置器件选型CAN设计硬件设计软件设计 485设计硬件设计软件设计 其他设计LED硬件按键硬件 PCB板子和实物图开发板测试视频其他资料 双CAN、一路485开发板的设计 最近工作经常会出现一些小问题。就想设计一款带CAN的开发板用来测试…...

快排/堆排/归并/冒泡/

常见的内排序算法 插入排序 直接插入排序 原理&#xff1a;相当于扑克牌变成有序&#xff0c;先拿第一张&#xff0c;把他调节成有序&#xff0c;再拿第二张&#xff0c;与第一张相比找到第二张的位置&#xff0c;再继续拿第三张&#xff0c;以此类推。 void InsertSort(in…...

React基础教程(08):state体验

文章目录 7、state再体验7.1 异步更新状态7.2 同步更新状态方式17.3 同步更新状态方式27.4 betterScroll7.5 列表案例7、state再体验 7.1 异步更新状态 完整代码 import React from "react";export default class App extends React.Component{state = {count:1,}…...

Win10 创建新的桌面2,并实现桌面切换

1. Win10 创建新的桌面2 Win - Tab 2. Win10 桌面切换 Ctrl - Win - ←/→ 我们下期见&#xff0c;拜拜&#xff01;...

MySQL数据库介绍及基础操作

目录&#xff1a; 一.数据库介绍 二.数据库分类 三. 数据库的操作 四. 常用数据类型 五. 表的操作 一.数据库介绍 1.文件保存数据有以下几个缺点: 1.1文件的安全性问题 1.2文件不利于数据查询和管理 1.3文件不利于存储海量数据 1.4文件在程序中控制不方便 为了解决上述问题&…...

【C语言篇】C语言常考及易错题整理DAY2

文章目录 C语言常考及易错题整理选择题编程题至少是其他数字两倍的最大数两个数组的交集图片整理寻找数组的中心下标多数元素除自身以外数组的乘积不使用加减乘除求两个数的加法 C语言常考及易错题整理 选择题 下列 for 循环的次数为&#xff08; &#xff09; for(int i 0…...

javase入门

最近在学习大数据,学到flume拦截器的时候发现自定义拦截器需要使用java编写,现在开始学一些java入门的东西. 一. java相关组成 path环境变量: 环境变量用于记住程序路径,方便在命令行窗口任意目录启动程序. 二 java中的变量 变量要先定义在使用. int age 15 定义变量要定义其…...

Wireshark显示过滤器大全:快速定位网络流量中的关键数据包

文章目录 一、简介二、wireshark中的逻辑运算符三、过滤示例集合3.1 过滤指定日期和时间3.2 过滤指定协议3.2.1 例&#xff1a;仅显示SMTP&#xff08;端口 25&#xff09;和ICMP流量&#xff1a;3.2.2 例如&#xff1a;Windows 客户端 - DC 交换 3.3 过滤指定网段&#xff08;…...

OOP笔记4----抽象类、接口、枚举

抽象类 简介 父类可以封装不同子类的共同特征或者共同行为.而有的时候&#xff0c;父类中封装的方法无法具体完成子类中需要的逻辑&#xff0c;因此我们可以将此方法设计成抽象方法&#xff0c;即使用关键字abstract进行修饰。而有抽象方法的类&#xff0c;也必须使用abstract…...