Vídeo de referência: Vídeo
Blog de referência: Blog
A divisão e conquista de pontos é um tipo de divisão e conquista de árvores, é uma arma poderosa para lidar com o problema do caminho em uma árvore em grande escala
Outro tipo de divisão e conquista de árvores é dividir e conquistar, um pouco mais problemático, quase todos os pontos divididos e conquistados podem dividir e conquistar
Existem dois caminhos em uma árvore: através do nó raiz e sem o nó raiz
Se excluirmos o nó raiz, podemos gerar várias subárvores com o filho do nó raiz original como o nó raiz
A subárvore é dividida em: através do nó raiz e sem o nó raiz
Etapas do algoritmo de divisão e conquista de pontos:
1. Processe o caminho atual através do nó raiz
2. Exclua o nó raiz
3. Gere cada subárvore e repita as etapas 1. e 2.
Por falar nisso, você provavelmente sabe por que sempre que deseja se concentrar na subárvore
Quanto a como encontrar o centro de gravidade, Baidu, não vou repeti-los aqui
Quanto a como mesclar estatisticamente as subárvores e contar as respostas, geralmente existe outro algoritmo, como uma matriz em árvore ou uma árvore de segmentos de linha, com reversibilidade (porque existe uma operação de exclusão do ponto raiz)
Vamos dar uma olhada em uma pergunta e postar um modelo básico
Dada uma árvore de n nós, cada aresta tem um peso de aresta e encontre o número de pares de pontos cuja distância entre dois pontos na árvore é menor ou igual a 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;
}