点分治用来解决树上距离的问题。
最经典,最模板的一道题:给定一棵树,给定一棵有n个点的树,询问树上距离为k的点对是否存在。
对于上面那个问题,我们可以O(n^2)枚举点对,然后对每个点对跑一遍LCA,复杂度是O(n^2·log(n))的,这显然不能让我们接受。那么,我们的点分治可以解决这类问题:首先,我们对每个点进行分析,对于每个点,我们有两种情况:
*1、经过这个点的路径
*2、不经过这个点,且在这个点的子树中的路径
点分治的思想就是对于每个点处理情况1,然后进行分治,分治到它的儿子节点再处理情况1,不断这样分治下去,就把情况2解决了。
也就是说,我们不断求情况1,对于情况2,它肯定会在这个点的子树中经过情况1而解决,那么我们就找到这棵子树的根,再对他求一次情况1。
那么问题来了,如果这棵树退化成链呢?从根一层一层往下分治显然是不优的,需要分治n层,这时候,就需要我们的重心:它能让哪怕是链上的分治也是log的,因为重心把树分成n/2,n/2个节点。至于正确性,我们把重心拿掉就是两棵分别独立的树,它们之间互不影响,那么正确性肯定是不影响的。
综上,我们把点分治大致思路了解了,那么接下来是细节:
我们令judge[dis]表示在rt子树v[1~k]中是否存在某个节点到rt距离为dis,也就是目前遍历的子树中是否存在到rt距离为dis,然后对于接下来的一棵子树,他到rt的距离为rem[i],若当前询问距离为que[k],那么我们只需要判断judge[que[k]-rem[i]]是否等于1即可。
再具体点解释这段话操作的含义,就是用子树vi中某个结点与子树v[1~i-1]中某个节点两两配对,检查是否存在长度为que[k]的路径。
像这样配对完后将这棵子树的rem(即子树vi中每个节点到rt的dis)一起保存进judge数组,继续下一个子树v[i+1]的处理,它有一个很大的好处就是所有边都只会遍历一遍,不会有重复情况,所有不用考虑去重。但是因为judge是个桶,所以如果边权太大就不能用这种方法了。
当以rt为根的树查询完后清空judge数组, 然后对其他子树进行分治
代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
const int MaxK=20000000;
const int MaxN=20000;
const int MaxM=200;
int n,m,que[MaxM];
int last[MaxN],tot;
int mxrt[MaxN],size[MaxN],vis[MaxN],nmax,sum,rt;
int rem[MaxN],sre,numb[MaxM],q[MaxN],dis[MaxN];
bool judge[MaxK];//长度是最长的边MaxK
struct edge{
int to,next,w;
}e[MaxN*4];
inline void add_edge(int x,int y,int w){
e[++tot].to=y;e[tot].next=last[x];e[tot].w=w;last[x]=tot;
return;
}
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline void getrt(int x,int fa){
mxrt[x]=0;size[x]=1;
for(int i=last[x];i;i=e[i].next){
int u=e[i].to;
if(u==fa||vis[u]) continue;
getrt(u,x);size[x]+=size[u];
mxrt[x]=max(mxrt[x],size[u]);
}
mxrt[x]=max(mxrt[x],sum-size[x]);
if(mxrt[x]<nmax) nmax=mxrt[x],rt=x;
return;
}
inline void dfs(int x,int fa){
rem[++sre]=dis[x];
for(int i=last[x];i;i=e[i].next){
int u=e[i].to;
if(u==fa||vis[u]) continue;
dis[u]=dis[x]+e[i].w;
dfs(u,x);
}
return;
}
inline void calc(int x){
int ls=0;
for(int i=last[x];i;i=e[i].next){
int u=e[i].to;
if(vis[u]) continue;
dis[u]=e[i].w;//以x为根,它子树到x的距离巧妙的赋值省去清零操作
sre=0;dfs(u,x);
for(int j=1;j<=m;++j){
for(int k=1;k<=sre;++k){
if(que[j]>=rem[k]){
if(judge[que[j]-rem[k]]){
numb[j]=1;break;
}
}
}
}
for(int j=1;j<=sre;++j) q[++ls]=rem[j],judge[rem[j]]=1;
}
for(int i=1;i<=ls;++i) judge[q[i]]=0;//记得清空
return;
}
inline void solve(int x){//对于每个点开始分治
vis[x]=judge[0]=1;calc(x);
for(int i=last[x];i;i=e[i].next){
int u=e[i].to;
if(vis[u]) continue;
nmax=0x3f3f3f3f;sum=size[u];
getrt(u,x);solve(rt);
}
return;
}
int main(){
int a,b,c;
n=read();m=read();
for(int i=1;i<=n-1;++i){
a=read();b=read();c=read();
add_edge(a,b,c);add_edge(b,a,c);
}
for(int i=1;i<=m;++i) que[i]=read();
nmax=0x3f3f3f3f;sum=n;getrt(1,0);solve(1);
for(int i=1;i<=m;++i){
if(numb[i]) puts("AYE");
else puts("NAY");
}
return 0;
}