【学习】点分治

元气小坏坏 提交于 2019-12-27 06:37:42

点分治是一种基于分治的算法

整体思想为不断删根把一棵较大的树拆成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;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!