【学习笔记】长链剖分

简述

用于优化于深度状态有关的 dp 转移,时空复杂度均为 O(n) 。其中每个节点的状态是用指针分配内存。

CF1009F Dominant Indices

题意:设 d(u,x)u 子树中到 u 距离为 x 的节点数。对于每个点,求一个最小的 k,使得 d(u,k) 最大。

#include<bits/stdc++.h>
using namespace std;
const int mx=1e6+5;
vector<int> g[mx];
//内存池 
int buf[mx];
//指针动态分配空间
int *f[mx],*now=buf,dep[mx],son[mx],ans[mx];
int n; 
void dfs1(int x,int fa) {
	for(auto y:g[x]) {
		if(y==fa) continue;
		dfs1(y,x);
		if(dep[y]>dep[son[x]]) son[x]=y;
	}
	dep[x]=dep[son[x]]+1;
} 
void dfs2(int x,int fa) {
	f[x][0]=1;
	if(son[x]) {
		f[son[x]]=f[x]+1;
		dfs2(son[x],x);
		ans[x]=ans[son[x]]+1;
	}
	for(auto y:g[x]) {
		if(y==fa||y==son[x]) continue;
		f[y]=now; now+=dep[y];
		dfs2(y,x);
		for(int j=1;j<=dep[y];j++) {
			f[x][j]+=f[y][j-1];
			if(f[x][j]>f[x][ans[x]]||f[x][j]==f[x][ans[x]]&&j<ans[x]) ans[x]=j;
		}
	}
	//细节问题 
	if(f[x][ans[x]]==1) ans[x]=0;
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<n;i++) {
		int x,y; scanf("%d%d",&x,&y);
		g[x].push_back(y),g[y].push_back(x);
	}
	dfs1(1,0); 
	f[1]=now; now+=dep[1];
	dfs2(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
}

[POI2014]HOT-Hotels

好难啊。。。

定义 f[u][j] 为以 u 为根的子树,距离为 j 的节点个数。g[u][j] 定义为下图 (x,y) 点对个数:

请添加图片描述
枚举子节点 v 转移:

  1. g[u][j]*f[v][j-1]->ans
  2. g[v][j]*f[u][j+1]->ans
  3. f[u][j]->f[v][j+1]

比较棘手的是 g[u][j] 的转移,分类讨论:

  1. (x,y) 在同一子树内,g[v][j]->g[u][j-1]
    请添加图片描述
  2. (x,y) 不在同一子树内,用 f[] 转移:f[v][j-1]*f[u][j]->g[u][j]请添加图片描述
    注意 g[]f[] 先转移。朴素转移是 O(n) ,时空复杂度 O(n^2)

可以用长链剖分优化。具体地,先继承重儿子的状态,考虑 ans+=g[u][0] 。再暴力转移轻儿子。注意 g 指针要前移。

时空复杂度均为 O(n)

总结:本题第一眼是换根 dp ,但是无法优化。本题巧妙利用状态设置+长链剖分,将距离作为状态,成功优化到线性。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mx=1e6+5;
vector<int> G[mx];
int buf[mx<<2];
int *f[mx],*g[mx],*now=buf,dep[mx],son[mx];
int n; int res; 
//长链剖分优化 dp
//uodate : 2021-07-23 
void dfs1(int u,int fa) {
	for(auto v:G[u]) {
		if(v==fa) continue;
		dfs1(v,u);
		if(dep[v]>dep[son[u]]) son[u]=v;
	}
	dep[u]=dep[son[u]]+1;
} 
void dfs2(int u,int fa) {
	f[u][0]=1;
	if(son[u]) {
		f[son[u]]=f[u]+1;
		g[son[u]]=g[u]-1; //g[v][j]->g[u][j-1]
		dfs2(son[u],u);
	}
	res+=g[u][0];
	for(auto v:G[u]) {
		if(v==fa||v==son[u]) continue;
		f[v]=now; now+=dep[v]<<1;
		g[v]=now; now+=dep[v]<<1;
		dfs2(v,u);
		for(int j=1;j<=dep[v];j++) {
			res+=g[u][j]*f[v][j-1]+g[v][j]*f[u][j-1];
		}
		for(int j=0;j<dep[v];j++) {
			g[u][j]+=g[v][j+1];
		}
		for(int j=1;j<=dep[v];j++) {
			g[u][j]+=f[v][j-1]*f[u][j];
		}
		for(int j=1;j<=dep[v];j++) {
			f[u][j]+=f[v][j-1];
		}
	}
}
signed main() {
	scanf("%lld",&n);
	for(int i=1;i<n;i++) {
		int x,y; scanf("%lld%lld",&x,&y);
		G[x].push_back(y),G[y].push_back(x);
	}
	dfs1(1,0);
	f[1]=now; now+=dep[1]<<1;
	g[1]=now; now+=dep[1]<<1;
	dfs2(1,0);
	printf("%lld",res);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值