[动态规划] 二次扫描与换根法

二次扫描与换根法

一些定义

  • 所谓二次扫描与换根法,就是一种处理无根树问题的方法

  • 算法特点:

  1. 第一次扫描时。在 “有根树” 上进行一次 树形DP 用来处理关键数据(准备工作),进行一次 \(dfs\) ,此过程是 自下及上 的,符合常规
  2. 第二次扫描时。在刚才选定的根上进行 自上及下 的推导,借助第一步处理的信息完成 换根后的解

但看理论不行,看下面一道题:

【POJ 3585】Accumulation Degree

题意

  • 在树上求最大流问题:选一个点作为源点,度数为 \(0\) 的点(叶子)作为汇点。
  • 问选取哪个点做源点可以获得最大流量?

题解

最朴素的方法,便是枚举所有点作为源点(树的根),将其转化为有根树进行求解。

不难发现,在有根树中每个结点的流域就是它的子树,因此我们处理出一个数组 \(D[s]\),表示\(s\) 为根的子树中,把 \(s\) 作为源点,从 \(s\) 出发流向子树的流量最大是什么:

\(D[x]=\sum_{y\in son(x)}\left\{\begin{aligned} min(D[y],c(x,y))\ \ deg[y]>1 \\ c(x,y)\ \ deg[y]=1 \end{aligned}\right.\)

很好理解,只需要考虑儿子是不是汇点即可。

然后我们需要根据维护出的东西进行最后的答案求解(在思考的时候就应该明白我处理出 \(D\) 数组的目的是什么,对我的有什么用没用写它干啥


显然的,我们把答案存在 \(f\) 数组里,其中 \(f[x]\) 表示 \(x\) 作为源点,向整个“无根树”的最大流量是多少

不难发现,根据我们所维护的 \(D\) 数组可以进行 \(f\) 的处理

由于便利是从上到下的,将问题缩放,假设已经处理出了 \(f[x]\) , 如何得到 $f[y] $ ?

  • \(f[y]\) 分成两部分:

  • 一部分为流向自己的子树,这个我们已经处理出

  • 另一部分为流向他的父亲:由于 \(x\)\(y\) 的流量为 \(min(c(x,y),D[y])\) ,这一部分的流量即 \(F[x]-min(c(x,y),D[y])\)

所以不难推出递推式:

\(F[y]=D[y]+\left\{\begin{aligned} min(F[x]-min(c(x,y),D[y]),c(x,y))\ \ deg[x]>1 \\ c(x,y)\ \ deg[x]=1\end{aligned} \right.\)

显然地,\(f[root]=d[root]\)


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 100;
#define LL long long
struct edge{
	int to,nxt;
}e[maxn<<1];
int val[maxn<<1];
int T,n,deg[maxn],f[maxn],d[maxn];
int head[maxn],cnt=0;
inline void link(int u,int v,int w){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;val[cnt]=w;
}
inline void clear(){
	for(int i=1;i<=cnt;i++)e[i].to=e[i].nxt=0;
	cnt=0;
	memset(val,0,sizeof val);
	memset(f,0,sizeof f);
	memset(d,0,sizeof d);
	memset(head,0,sizeof head);
	memset(deg,0,sizeof deg);
}
void dp(int u,int fa){
	//cerr<<"@"<<endl;//
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dp(v,u);
		if(deg[v]>1)d[u]+=min(d[v],val[i]);
		else d[u]+=val[i];
	}
}
void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		if(deg[u]>1){
			f[v]=d[v]+min(f[u]-min(val[i],d[v]),val[i]);
		}
		else f[v]=d[v]+val[i];
		dfs(v,u);
	}
}
void solve(){
	int rt=1;
	//for(int i=1;i<=n;i++)printf("deg[%d]: %d\n",i,deg[i]);
	dp(rt,0);
	f[rt]=d[rt];
	//for(int i=1;i<=n;i++)printf("d[%d]: %d\n",i,d[i]);//
	dfs(rt,0);
	//for(int i=1;i<=n;i++)printf("f[%d]: %d\n",i,f[i]);//
	int ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,f[i]);
	printf("%d\n",ans);
}
int main(){
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		clear();
		scanf("%d",&n);
		for(int i=1,u,v,w;i<n;i++){
			scanf("%d%d%d",&u,&v,&w);
			link(u,v,w);link(v,u,w);
			deg[u]++;deg[v]++;
		}
		solve();
	}
	return 0;
}

总结

  • 二次扫描与换根法经常用来解决 不定根问题

  • 它可以通过两次 \(dfs\) 扫描将 所有子树 的答案处理出来

posted @ 2021-08-12 17:10  ¶凉笙  阅读(262)  评论(0编辑  收藏  举报