[模板] 树的重心/点分治/动态点分治

树的重心

树的其他问题 - OI Wiki

定义

\((1)\) 树的重心 \(\leftrightarrow\) 所有的子树中最大的子树节点数最少的节点.

\((2)\) 树的重心 \(\leftrightarrow\) 所有子树的大小都不超过整个树大小的一半的节点.

性质

删去重心后, 生成的多棵树尽可能平衡.

一棵树中最少有两个重心.

树中所有点到某个点的距离和中, 到重心的距离和是最小的; 如果有两个重心, 那么他们的距离和一样.

把两个树通过一条边相连得到一个新的树, 那么新的树的重心在连接原来两个树的重心的路径上.

把一个树添加或删除一个叶子, 那么它的重心最多只移动一条边的距离.

求法&&代码

根据定义\((1)\), dfs求即可.

int szp[nsz],sum,maxp[nsz]{2e4+5},rt=0;

void getrt(int p,int fa){
	szp[p]=1,maxp[p]=0;
	forg(p,i,v){
		if(v==fa)continue;
		getrt(v,p);
		szp[p]+=szp[v];
		maxp[p]=max(maxp[p],szp[v]);
	}
	maxp[p]=max(maxp[p],sum-szp[p]);
	if(maxp[p]<maxp[rt])rt=p;
}

点分治

简介

点分治可以解决一些树上的路径问题.

对于一棵无根树, 可以将一个点 \(rt\) 设为根, 把它转化为有根树. 那么树上的路径可以分为两类:

  1. 经过根的路径;
  2. 仅在某个子树内的路径.

对于1, 我们可以将链 \((a,b)\) 分为 \((a,rt)\), \((rt,b)\), 多数情况可以合并他们的影响 (例如求路径长度). 一般有两种做法:

  • 处理 \(rt\) 及其所有子节点, 求出两两之间的对答案的贡献, 再减去同一子树内的点对对答案的贡献.

  • 分别递归 \(rt\) 的每个子树, 对每个子树求出其与之前的子树中的节点形成点对的贡献. 这样就不需要容斥减去重复的部分.

对于2, 可以递归求解.

可以证明, 如果在递归每个子树时都将子树的重心设为根, 那么递归层数是 \(O(\log n)\) 的.

代码

//store the tree
const int nsz=20050;

ll n;
struct te{int t,pr,v;}edge[nsz*2];
int hd[nsz],pe=1;
void adde(int f,int t,int v){edge[++pe]=(te){t,hd[f],v};hd[f]=pe;}
void adddb(int f,int t,int v){adde(f,t,v);adde(t,f,v);}
#define forg(p,i,v) for(int i=hd[p],v=edge[i].t;i;i=edge[i].pr,v=edge[i].t)

//find center of tree
int vi[nsz];
int szp[nsz],sum,maxp[nsz]{2e4+5},rt;

void getrt(int p,int fa){
	szp[p]=1,maxp[p]=0;
	forg(p,i,v){
		if(vi[v]||v==fa)continue;
		getrt(v,p);
		szp[p]+=szp[v];
		maxp[p]=max(maxp[p],szp[v]);
	}
	maxp[p]=max(maxp[p],sum-szp[p]);
	if(maxp[p]<maxp[rt])rt=p;
}

void sol1(int rt,int v0,int fl){
//do something
}

void divide(int p){
	int sum0=sum;
	vi[p]=1;
	sol1(p,0,1);
	forg(p,i,v){
		if(vi[v])continue;
		sol1(v,edge[i].v,-1);
		rt=0,sum=sum0-maxp[v],getrt(v,0);
		divide(rt);
	}
}

ll sol(){
	rt=0,sum=n,getrt(1,0); //init
	divide(rt);
	return ans;
}

例题

bzoj2152-[国家集训队]聪聪可可

动态点分治

posted @ 2019-02-20 19:43  Ubospica  阅读(347)  评论(0编辑  收藏  举报