长链剖分

47 篇文章 0 订阅
39 篇文章 0 订阅

终于填坑了呢

长链剖分类似于每个点选最深的儿子作为重儿子的重链剖分。
树边长度均为 1 1 1的长链剖分有一些优美的性质。

可以维护以深度为下标的 D P DP DP
例:Hotel加强版
有一个树形结构,每条边的长度相同,任意两个节点可以相互到达。选3个点。两两距离相等。有多少种方案?

这个题我们可以写出一个 O ( n 2 ) O(n^2) O(n2) D P DP DP
f i , j f_{i,j} fi,j表示 i i i子树内与 i i i的距离为 j j j的点数。
g i , j g_{i,j} gi,j表示 i i i子树内有多少对 ( a , b ) (a,b) (a,b)满足 d e p a = d e p b dep_a = dep_b depa=depb并且 i i i往上(往祖先)走 j j j步之后的点 x x x满足 ( a , b , x ) (a,b,x) (a,b,x)两两距离相等。

f i , j = ∑ v ∈ s o n i f v , j − 1 , f i , 0 = 1 f_{i,j} = \sum_{v \in son_i} f_{v,j-1} , f_{i,0} = 1 fi,j=vsonifv,j1,fi,0=1
g i , j = ∑ v ∈ s o n i g v , j + 1 + ∑ u , v ∈ s o n i f u , j − 1 f v , j − 1 g_{i,j} = \sum_{v \in son_i} g_{v,j+1} + \sum_{u,v\in son_i} f_{u,j-1}f_{v,j-1} gi,j=vsonigv,j+1+u,vsonifu,j1fv,j1

这个可以用长链剖分优化到 O ( n ) O(n) O(n)
首先解决空间问题,我们需要在树上重复利用 d p dp dp数组。
发现长链剖分后,每个点的数组第二维大小为其重儿子 + 1 +1 +1
那么对于 f i , j f_{i,j} fi,j,我们完全可以让 i i i继承 i i i的重儿子 v v v f v , j f_{v,j} fv,j,然后再把轻儿子的 f f f并入计算转移。
只需要将 f i , j = f v , j + 1 , f i , 0 = 1 f_{i,j} = f_{v,j+1} , f_{i,0} = 1 fi,j=fv,j+1,fi,0=1,第一个操作可以通过关于内存指针,分配地址的操作做到 O ( 1 ) O(1) O(1),就是简单的* f i = f v − 1 f_{i} = f_{v} - 1 fi=fv1,只是 f v f_{v} fv会在之后的 D P DP DP中被破坏,也就是说这种处理方式不支持可持久化。
对于 g g g,需要 g i , j = g v , j − 1 g_{i,j} = g_{v,j-1} gi,j=gv,j1,这个也可以用地址分配做到 O ( 1 ) O(1) O(1),但是需要开两倍空间。

关于轻儿子 v v v合并到 i i i,可以做到 O ( v 子 树 中 的 最 大 深 度 − d e p i ) O(v子树中的最大深度 - dep_i) O(vdepi),接下来我们可以通过证明 ∑ v 是 f a v 的 轻 儿 子 v 子 树 中 的 最 大 深 度 − d e p v = O ( n ) \sum_{v是fa_v的轻儿子} v子树中的最大深度 - dep_{v} = O(n) vfavvdepv=O(n)来证明我们算法的时间复杂度为 O ( n ) O(n) O(n),至于证明,一条长链的链头 v v v v 子 树 中 的 最 大 深 度 − d e p v = v子树中的最大深度 - dep_v= vdepv=长链的大小,所以总和就是总点数 O ( n ) O(n) O(n)

A C   C o d e \rm{AC\ Code} AC Code

#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;
int n,len[maxn],info[maxn],son[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e=0;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
LL tf[maxn],tg[maxn<<1],*f[maxn]={tf},*g[maxn]={tg},ans;
void dfs1(int u,int ff){
	for(int i=info[u],v;i;i=Prev[i])
		if((v=to[i])!=ff){
			dfs1(v,u);
			if(len[son[u]] < len[v]) son[u] = v;
		}
	len[u] = len[son[u]] + 1;
}
void dfs2(int u,int ff){
	if(son[u]) f[son[u]] = f[u] + 1 , g[son[u]] = g[u] - 1 , dfs2(son[u],u);
	f[u][0] = 1,ans += g[u][0];
	for(int i=info[u],v;i;i=Prev[i]){
		if((v=to[i])!=ff && v!=son[u]){
			f[v] = f[0] , f[0] += len[v];
			g[v] = g[0] + len[v] - 1 , g[0] += 2 * len[v] - 1;
			dfs2(v,u);
			for(int j=0;j<len[v];j++)
				ans += f[v][j] * g[u][j+1] + (j>0 ? g[v][j] * f[u][j-1] : 0);
			for(int j=0;j<len[v];j++){
				g[u][j+1] += f[u][j+1] * f[v][j];
				f[u][j+1] += f[v][j];
				if(j) g[u][j-1] += g[v][j];
			}
		}
	}
}

int main(){
	scanf("%d",&n);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		Node(u,v),Node(v,u);
	}
	dfs1(1,0);
	f[1] = f[0] , f[0] += len[1];
	g[1] = g[0] + len[1] - 1 , g[0] += 2 * len[1] - 1;
	dfs2(1,0);
	printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值