模板:长链剖分

61 篇文章 0 订阅
46 篇文章 0 订阅

所谓长链剖分,就是对长链进行剖分

(逃)

前言

很优雅的算法
利用对指针进行魔法操作将 n 2 n^2 n2 的 dp 优化成线性
线性啊!!!

解析

CF1009F Dominant Indices

给定一棵以 1 1 1 为根, n n n 个节点的树。设 d ( u , x ) d(u,x) d(u,x) u u u 子树中到 u u u 距离为 x x x 的节点数。
对于每个点,求一个最小的 k k k,使得 d ( u , k ) d(u,k) d(u,k) 最大。

长剖板子题
f x , d f_{x,d} fx,d 表示x子树内到x距离为d的结点个数
则有:
f x , d ← f s o n , d − 1 f_{x,d}\gets f_{son,d-1} fx,dfson,d1
所以其实就是下标偏离了一位而已
那么如何解决呢?

长链剖分

定义一个子树的深度是子树内深度最大的儿子的深度
定义一个结点的重儿子是子树深度最大的儿子
和重链剖分类似的,重儿子连成重链,单独结点也算一个重链,每个结点都在唯一的重链上

void dfs1(int x,int fa){
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==fa) continue;
		dfs1(to,x);
		if(1) son[x]=to;
	}
	len[x]=len[son[x]]+1;
}

指针偏移

既然我们的父亲dp值只是和儿子相比偏移了一位
那么我们就尝试利用指针的便宜让父亲直接继承儿子的dp
当然,继承所有儿子的dp是无法实现的,那么我们就让父亲继承重儿子的dp值,其他儿子暴力转移

void dp(int x,int fa){
	f[x][0]=1;
	if(son[x]){
		f[son[x]]=f[x]+1;
		dp(son[x],x);
		ans[x]=ans[son[x]]+1;
	}
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==fa||to==son[x]) continue;
		f[to]=now;now+=len[to];
		dp(to,x);
		for(int j=0;j<len[to];j++){
			f[x][j+1]+=f[to][j];
			if(f[x][j+1]>f[x][ans[x]]||(f[x][j+1]==f[x][ans[x]]&&j+1<ans[x])) ans[x]=j+1;
		}
	}
	if(f[x][ans[x]]==1) ans[x]=0;
}

复杂度证明

每个重链从链头向父亲转移,会产生重链长度的代价
由于重链长度和为结点个数,所以复杂度为 O ( n ) O(n) O(n)

注意! 和重链剖分不同,每个结点到根节点的长链个数可以达到 O ( n ) O(\sqrt n) O(n ) 级别

完整代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1e6+100;
const double eps=1e-12;
inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

int n;
struct node{
	int to,nxt;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y){
	p[++cnt]=(node){y,fi[x]};fi[x]=cnt;
	return;
}
int son[N],len[N];
int buf[N],*f[N],*now=buf;
void dfs(int x,int f){
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f) continue;
		dfs(to,x);
		if(len[to]>len[son[x]]) son[x]=to;
	}
	len[x]=len[son[x]]+1;
}
int ans[N];
void dp(int x,int fa){
	f[x][0]=1;
	if(son[x]){
		f[son[x]]=f[x]+1;
		dp(son[x],x);
		ans[x]=ans[son[x]]+1;
	}
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==fa||to==son[x]) continue;
		f[to]=now;now+=len[to];
		dp(to,x);
		for(int j=0;j<len[to];j++){
			f[x][j+1]+=f[to][j];
			if(f[x][j+1]>f[x][ans[x]]||(f[x][j+1]==f[x][ans[x]]&&j+1<ans[x])) ans[x]=j+1;
		}
	}
	if(f[x][ans[x]]==1) ans[x]=0;
}
signed main(){
#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
#endif
	memset(fi,-1,sizeof(fi));cnt=-1;
	n=read();
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		addline(x,y);addline(y,x);
	}
	dfs(1,0);
	f[1]=now;now+=len[1];dp(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
}
/*

*/

Thanks for reading!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值