2020.07.21日常总结——换根dp略讲

5 篇文章 0 订阅
本文介绍了无根树上的动态规划问题解决技巧——换根DP,通过例题P2986 [USACO10MAR]Great Cow Gathering G,阐述了如何在O(n)的时间复杂度内求解所有节点为根时的不方便值,总结了解题步骤,并提供了相关资源链接。
摘要由CSDN通过智能技术生成

换根dp \color{green}{\texttt{换根dp}} 换根dp

严格来说,其实并没有 换根dp 这一种 dp 方法,它只是 树上dp 问题的一个解题技巧。但是由于这个技巧太过于重要了,所以我们可以把它叫做一个单独类型的 dp 方法。

所谓的 换根dp,是针对无根树上 dp 问题的一个解题技巧。

众所周知,我们解无根树上 dp 问题时一般会先强制一个节点(一般来说,为了方便,会选定 1 1 1 号点)当整棵树的根,然后在考虑求解。

但是,如果要求出所有点做根时的 dp 值该怎么办呢?暴力 O ( n ) O(n) O(n) 枚举?不是不行,但是在大多数时候TLE(注意,我没有说一定不行!)。

dp 题的一大特色是:理论非常难讲懂,所以我们直接上一道例题。


P2986   [USACO10MAR]Great   Cow   Gathering   G \color{green}{\texttt{P2986 [USACO10MAR]Great Cow Gathering G}} P2986 [USACO10MAR]Great Cow Gathering G

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

在这里插入图片描述

在这里插入图片描述

[solution] \color{blue}{\texttt{[solution]}} [solution]

最原始的想法是一个 O ( n 2 ) O(n^2) O(n2) 的暴力: O ( n ) O(n) O(n) 枚举节点, O ( n ) O(n) O(n) 求出所有牛到一个节点的不方便值。

可是 1 ≤ n ≤ 1 × 1 0 5 1 \leq n \leq 1 \times 10^5 1n1×105 O ( n 2 ) O(n^2) O(n2) 这样的算法实在是太慢了。有没有快点的方法?

有。

我们记 f u f_u fu 表示所有牛到 u u u 这个节点的不方便值,则:

f u = ∑ i = 1 n C i × s i , u f_u=\sum\limits_{i=1}^{n} C_i\times s_{i,u} fu=i=1nCi×si,u

其中, s i , u s_{i,u} si,u 表示 i i i u u u 之间的距离。

我们再记 s z u sz_u szu 表示 u u u 的子树的大小(注意:这个大小指的是有多少头牛而非有多少个节点)。

显然,我们会先指定一个节点当根(这个根就是所有奶牛要到的节点),我们不妨就指定 1 1 1 吧, O ( n ) O(n) O(n) 递归求出 s z i ( 1 ≤ i ≤ n ) sz_i(1 \leq i \leq n) szi(1in) f 1 f_1 f1

如何用 f 1 f_1 f1 s z sz sz 推导出其它的 f i ( 2 ≤ i ≤ n ) f_i(2 \leq i \leq n) fi(2in) 呢?我们发现这个移动节点的过程就是无根树移动根的过程,我们可以画这么一幅图:

在这里插入图片描述

这就是一个换根的过程。

它能告诉我们一些什么呢?首先我们发现以 3 3 3 为根的子树内的所有的点需要做得距离少了 s 1 , 3 s_{1,3} s1,3,但那些不是 3 3 3 的儿子的点(包括 1 1 1),就需要多走 s 1 , 3 s_{1,3} s1,3 的距离。

所以,在这幅图中,我们有:

f 3 = f 1 − s z 3 × s 1 , 3 + ( n − s z 3 ) × s 1 , 3 f_{3}=f_1-sz_3 \times s_{1,3} + (n-sz_3) \times s_{1,3} f3=f1sz3×s1,3+(nsz3)×s1,3

类似地,我们可以推广到一般情况:

f v = f u − s z v × s u , v + ( n − s z v ) × s u , v f_v = f_u - sz_v \times s_{u,v}+(n-sz_v) \times s_{u,v} fv=fuszv×su,v+(nszv)×su,v

于是,我们再 dfs 一次,就可以在 O ( n ) O(n) O(n) 的时间复杂度内求出所有的 f i ( 1 ≤ i ≤ n ) f_i(1 \leq i \leq n) fi(1in) 了。

总时间复杂度: O ( n ) O(n) O(n)

[code] \color{blue}{\texttt{[code]}} [code]

const int N=1e5+100;
struct edge{//链式前向星 
	int next,to,len;
}e[N<<1];int h[N],tot;
inline void add(int a,int b,int c){
	e[++tot]=(edge){h[a],b,c};h[a]=tot;
	e[++tot]=(edge){h[b],a,c};h[b]=tot;
}
int sze[N],n,C[N],cnt;//含义如题 
long long F1,ans;//要开long long 
typedef long long ll;//为了方便 
void dfs1(int u,int fa,ll s){
	F1+=1ll*C[u]*s;sze[u]=C[u];
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		if (to==fa) continue;//!
		dfs1(to,u,s+e[i].len);
		sze[u]+=sze[to];//updata
	}
}
void dfs2(int u,int fa,ll Fu){
	for(int i=h[u];i;i=e[i].next){
		register int to=e[i].to;
		if (to==fa) continue;//!
		ll Fv=Fu-1ll*sze[to]*e[i].len
		      +1ll*(cnt-sze[to])*e[i].len;
		ans=min(ans,Fv);//更新答案 
		dfs2(to,u,Fv);//继续递归 
	}
}
int main(){
	n=read();F1=0;//init 
	for(int i=1;i<=n;i++)
		cnt+=(C[i]=read());
	for(int i=2;i<=n;i++){
		int u=read(),v=read(),w=read();
		add(u,v,w);//按照题目要求加边 
	}
	dfs1(1,-1,0);//求F1 
	ans=F1;//ans最大为F1 
	dfs2(1,-1,F1);//换根 
	printf("%lld",ans);
} 

总结 \color{green}{\texttt{总结}} 总结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值