点分治学习笔记

点分治用来解决树上距离的问题。

最经典,最模板的一道题:给定一棵树,给定一棵有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;
}

 

posted @ 2019-05-05 22:47  X_rice  阅读(163)  评论(0编辑  收藏  举报