点分治是一种基于分治的算法
整体思想为不断删根把一棵较大的树拆成n个小树再分别求解再合并
关于此题
我们先随意指定一个根,树上路径就分成了过根的和不过根在一个子树里的
这样经过根的路径即为dis[u]+dis[v],dis[i]是i到根的路径长度
不经过根的就再找这棵子树的根如此递归
显然分治
把一个无根树转化为有根树,找重心
void getrt(int u,int fa){//找根
sz[u]=1;maxp[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa||vis[v])continue;
getrt(v,u);
sz[u]+=sz[v];
maxp[u]=max(maxp[u],sz[v]);
}
maxp[u]=max(maxp[u],sum-sz[u]);
if(maxp[u]<maxp[rt])rt=u;
}
找重心的意义在于每次选取子树的重心为子树的树根进行处理, 这样总的递归深度不会超过logN层
以保证复杂度
预处理
void calc(int u){
int p=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v])continue;
rem[0]=0;
dis[v]=e[i].w;
getdis(v,u);//处理u的每个子树的dis
for(int j=rem[0];j;j--)//遍历当前子树的dis
for(int k=1;k<=m;k++){//遍历每个询问
if(query[k]>=rem[j])//如果query[k]-rem[j]d的路径存在就标记第k个询问
test[k]|=ju[query[k]-rem[j]]; //如果query[k]-rem[j]d的路径存在就标记第k个询问
}
for(int j=rem[0];j;j--){//保存出现过的dis于judge
q[++p]=rem[j];
ju[rem[j]]=1;
}
}
for(int i=1;i<=p;i++){
ju[q[i]]=0;
}
}
总复杂度O(NmlogN)
//
// main.cpp
// 【模版】点分治
//
// Created by gengyf on 2019/7/11.
// Copyright © 2019 yifan Geng. All rights reserved.
//
//洛谷P3086
/*
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
input
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
output
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)
*/
#include <bits/stdc++.h>
using namespace std;
const int inf=10000000;
const int maxn=100010;
int n,m,sum,rt,ans;
struct edge{
int nxt,to,w;
}e[maxn*2];
int cnt,head[maxn];
int maxp[maxn],sz[maxn],dis[maxn],rem[maxn];
//maxp[u]表示删除结点u后产生的子树中,最大的那棵的大小;sz[u]是以u为根的子树大小
//dis[u]表示结点u到根节点rt的路径长度,u到v的路径长即为dis[u]+dis[v]
//rem记录从当前根u出发向其中一个子树vi走能够得到的不同距离disint vis[maxn],test[inf],ju[inf],q[maxn];
//vis是用来处理子树中的重心的
//judge[i]表示到根距离为i的路径是否存在
int query[1010];//离线
void add(int from,int to,int w){
e[++cnt].to=to;e[cnt].nxt=head[from];e[cnt].w=w;head[from]=cnt;
}
//sum是当前子树的总结点数
void getrt(int u,int fa){//找根
sz[u]=1;maxp[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa||vis[v])continue;
getrt(v,u);
sz[u]+=sz[v];
maxp[u]=max(maxp[u],sz[v]);
}
maxp[u]=max(maxp[u],sum-sz[u]);
if(maxp[u]<maxp[rt])rt=u;
}
void getdis(int u,int fa){
rem[++rem[0]]=dis[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa||vis[v])continue;
dis[v]=dis[u]+e[i].w;
getdis(v,u);
}
}
void calc(int u){
int p=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v])continue;
rem[0]=0;
dis[v]=e[i].w;
getdis(v,u);//处理u的每个子树的dis
for(int j=rem[0];j;j--)//遍历当前子树的dis
for(int k=1;k<=m;k++){//遍历每个询问
if(query[k]>=rem[j])//如果query[k]-rem[j]d的路径存在就标记第k个询问
test[k]|=ju[query[k]-rem[j]]; //如果query[k]-rem[j]d的路径存在就标记第k个询问
}
for(int j=rem[0];j;j--){//保存出现过的dis于judge
q[++p]=rem[j];
ju[rem[j]]=1;
}
}
for(int i=1;i<=p;i++){//处理完这个子树就清空judge
ju[q[i]]=0;
}
}
void solve(int u){
vis[u]=ju[0]=1;
calc(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v])continue;
sum=sz[v];
maxp[rt=0]=inf;
getrt(v,0); solve(rt);//在子树中找重心并递归处理
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
for(int i=1;i<=m;i++){
scanf("%d",&query[i]);
}
maxp[rt]=sum=n;
getrt(1,0);
solve(rt);
for(int i=1;i<=m;i++){
if(test[i])printf("AYE\n");
else printf("NAY\n");
}
return 0;
}
来源:https://www.cnblogs.com/gengyf/p/11173538.html