【动态规划 & 换根dp】Change Root DP
还在更新ing
前言
此乃小 Oler 的一篇小小算法随笔,从今日后,还会进行详细的修订。
一、简单介绍(change root dp)
如需了解树形dp的,本蒟蒻毛遂自荐,万字大文动态规划【树形dp】Tree DP ~~~详解
换根dp,又叫二次扫描,是树形DP的一种,是一种用来求解树上各点到其他点的距离之和的算法。
其相比于一般的树形DP具有以下特点:
- 以树上的不同点作为根,其解不同。
- 故为求解答案,不能单求某点的信息,需要求解每个节点的信息。
- 故无法通过一次搜索完成答案的求解,因为一次搜索只能得到一个节点的答案。
二、代码实现
引入
T1. 深度最大
描述
给定一个
输入格式
第一行有一个整数,表示树的结点个数
接下来
输出格式
输出一行一个整数表示你选择的结点编号。如果有多个结点符合要求,输出节点编号最小值。
输入/输出样例
输入 #1
5
1 2
1 3
2 4
2 5
输出 #1
3
说明/提示
数据规模与约定
对于
思路
先简单说说题意,找出对于树上任意节点
I.初始化
由于这是一棵无根树,我们假设
①.
②.
③.
II.First Dfs
这个 dfs 是用于记录以
若子节点
否则,就和父节点
III.Second Dfs
第二个 dfs 真正意义上体现了“换根”,记录整棵树依次以树上任一节点为根的最大深度,子节点的状态是由父亲转移过来的,
这里第一次 dfs 所记录的
若父节点
反之,
深度最大
最后比较以
图例注释:红色箭头表示第一次 dfs 的转态转移,即
蓝色箭头表示第二次 dfs 的状态转移,即
Code T1
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,x,y,z;
struct Edge {
int to,nxt,w;
Edge() {
to=nxt=w=0;
}
Edge(int a,int b,int c) {
to=a;
nxt=b;
w=c;
}
}edge[N<<1];
int head[N],idx;
int g[N][2],f[N];
int d[N],ans,p;
void add(int x,int y,int z) { //邻接表存图
edge[++idx]=Edge(y,head[x],z);
head[x]=idx;
}
void dfs_down(int u,int fa) { //求以i为根的子树的最大深度
g[u][0]=g[u][1]=1;
for(int i=head[u];i;i=edge[i].nxt) {
int v=edge[i].to;
int w=edge[i].w;
if(v==fa) continue;
dfs_down(v,u);
if(g[v][0]+w>g[u][0]) {
g[u][1]=g[u][0];
g[u][0]=g[v][0]+w;
d[u]=v;
} else {
if(g[v][0]+w>g[u][1])
g[u][1]=g[v][0]+w;
}
}
return ;
}
void dfs_up(int u,int fa) { //求以i为跟的整棵树的最大深度
for(int i=head[u];i;i=edge[i].nxt) {
int v=edge[i].to;
int w=edge[i].w;
if(v==fa) continue;
f[v]=f[u]+w;
if(d[u]==v)
f[v]=max(g[u][1]+w,f[v]);
else f[v]=max(g[u][0]+w,f[v]);
dfs_up(v,u);
}
}
signed main() {
scanf("%lld",&n);
for(int i=1;i<n;i++) {
scanf("%lld%lld",&x,&y);
add(x,y,1);
add(y,x,1);
}
dfs_down(1,0);
dfs_up(1,0); //遍历整棵树
for(int i=1;i<=n;i++) { //取以i为子树和整棵树中的最大深度
int w=max(g[i][0],f[i]);
if(ans<w) ans=w,p=i; //记录节点编号
}
printf("%lld\n",p);
return 0;
}
T2. [POI2008] STA-Station
题目描述
给定一个
一个结点的深度之定义为该节点到根的简单路径上边的数量。
输入格式
第一行有一个整数,表示树的结点个数
接下来
输出格式
本题存在 Special Judge。
输出一行一个整数表示你选择的结点编号。如果有多个结点符合要求,输出任意一个即可。
样例 #1
样例输入 #1
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4
样例输出 #1
7
提示
样例 1 解释
输出
数据规模与约定
对于全部的测试点,保证
实现流程
①. 子树的深度和
先钦定
表示以 为根的子树的深度和。 表示以 为根的子树中的节点数量; 表示以 为根的子树的中的所有节点到根的距离和。
结合下图,已知
则易知
节点
由此可推出转移式:
②. 整棵树的深度和
遍历树上的所有节点
表示以 为根的整棵树的深度之和。
预处理:
我们可以通过下图的视角转换中可以发现,在以
由于还是从
再结合下图,可以把以
易推出方程式是:
化简而得:
Code T2
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,x,y;
struct Edge {
int to,nxt;
Edge() {
to=nxt=0;
}
Edge(int a,int b) {
to=a;
nxt=b;
}
}edge[N<<1];
int head[N],idx;
int f[N],dep[N];
int res[N],ans,p;
void add(int x,int y) {
edge[++idx]=Edge(y,head[x]);
head[x]=idx;
}
void dfs(int u,int fa) {
res[u]=1;
dep[u]=dep[fa]+1;
for(int i=head[u];i;i=edge[i].nxt) {
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
res[u]+=res[v];
}
}
void dfs_(int u,int fa) {
for(int i=head[u];i;i=edge[i].nxt) {
int v=edge[i].to;
if(v==fa) continue;
f[v]=f[u]+n-2*res[v];
dfs_(v,u);
}
}
signed main() {
scanf("%lld",&n);
for(int i=1;i<n;i++) {
scanf("%lld%lld",&x,&y);
add(x,y),add(y,x);
}
dfs(1,0);
for(int i=1;i<=n;i++)
f[1]+=dep[i];
dfs_(1,0);
for(int i=1;i<=n;i++) {
if(ans<f[i]) {
ans=f[i];
p=i;
}
}
printf("%lld\n",p);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!