参考视频:视频
参考博客:博客
点分治是树分治的一种,是处理大规模树上路径的问题的强有力武器
另一种树分治是边分治,稍麻烦些,几乎所有的点分治都可以边分治
一颗树上的路径分为两种: 经过根节点 和 不经过根节点的
若我们把根节点删去,则可以生成若干棵以原根节点的儿子为根节点的子树
子树又分为:经过根节点 和 不经过根节点的
点分治算法步骤:
1、处理当前经过根节点的路径
2、删除根节点
3、生成每棵子树重复1、 2、步骤
说到这 大概就知道为什么每次子树要找重心了吧
至于怎么找重心,自行百度,这里不再赘述
至于怎么统计合并子树 并统计答案,一般还要套一个其他的算法,比如树状数组,或者线段树,还要带可撤销的(因为有删除根点操作)
来看一道题,顺便贴贴基本模板
给定一棵 n 个节点的树,每条边有边权,求出树上两点距离小于等于 k 的点对数量。
include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int rd(){
int ret=0,f=1;char c;
while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return ret*f;
}
const int MAXN=262144;
const int INF=1<<29;
int n,m;
struct Edge{
int next,to,w;
}e[MAXN<<1];
int ecnt,head[MAXN];
inline void add(int x,int y,int w){
e[++ecnt].next = head[x];
e[ecnt].to = y;
e[ecnt].w = w;
head[x] = ecnt;
}
int t[MAXN];
void update(int x,int val){
for(int i=x;i<=m;i+=i&-i)t[i]+=val;
}
int query(int x){
if(x==0) return 0;
int ret=0;
for(int i=x;i;i-=i&-i)ret+=t[i];
return ret;
}
bool vis[MAXN];
int siz[MAXN];
void getsiz(int x,int pre){//预处理树上 每个节点的子树大小
siz[x]=1;
for(int i=head[x];i;i=e[i].next){
int v=e[i].to;
if(vis[v]||v==pre) continue;
getsiz(v,x);
siz[x]+=siz[v];
}
}
int mn=INF,root;
void getroot(int x,int pre,int tot){
int mx=0;
for(int i=head[x];i;i=e[i].next){
int v=e[i].to;
if(vis[v]||v==pre) continue;
mx=max(mx,siz[v]);
getroot(v,x,tot);
}
mx=max(mx,tot-siz[x]);
if(mx<mn) mn=mx,root=x;
}
int s[MAXN],sav[MAXN];
void dfs(int x,int pre,int dis){
if(dis>m) return;
s[++s[0]]=dis;
sav[++sav[0]]=dis;
for(int i=head[x];i;i=e[i].next){
int v=e[i].to;
if(vis[v]||v==pre) continue;
dfs(v,x,dis+e[i].w);
}
}
int ans;
void dac(int x){//Divide and Conquer :)
sav[0]=0;mn=n;
getsiz(x,-1);//以x为根节点 重新getsize 一下
getroot(x,-1,siz[x]);//先找到一个重心
int u=root;vis[u]=1;///删掉根节点
for(int i=head[u];i;i=e[i].next){//维护答案
int v=e[i].to;
if(vis[v]) continue;
s[0]=0;
dfs(v,u,e[i].w); //以u节点为根的 经过根节点的路径保存至s 和 sav里面
for(int j=s[0];j>=1;j--){
if(s[j]>m) continue;
ans+=query(m-s[j]);//权值树状数组
}
for(int j=s[0];j>=1;j--){
if(s[j]>m) continue;
update(s[j],1);//更新答案
ans++;
}
}
for(int i=sav[0];i>=1;i--){//删掉答案
if(sav[i]>m) continue;
update(sav[i],-1);
}
for(int i=head[u];i;i=e[i].next){//去掉根u 继续点分治
int v=e[i].to;
if(vis[v]) continue;
dac(v);
}
}
int main(){
n=rd();
int x,y,w;
for(int i=1;i<n;i++){
x=rd();y=rd();w=rd();
add(x,y,w);add(y,x,w);
}
m=rd();
dac(1);
cout<<ans;
return 0;
}