• 点分治学习笔记


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

    最经典,最模板的一道题:给定一棵树,给定一棵有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;
    }
  • 相关阅读:
    ubuntu 更新软件
    如何在linux(lubuntu)下搭建C/C++开发环境
    Linux下如何查看版本信息
    知识点笔记
    Require.js中使用jQuery 插件
    async中常用总结
    node.js在遇到“循环+异步”时的注意事项
    前端性能优化
    生产/消费者问题
    线程与内存
  • 原文地址:https://www.cnblogs.com/X-rice/p/10816888.html
Copyright © 2020-2023  润新知