P3806 点分治【模板】

给定一颗有n个点的树,询问树上距离为k的点对是否存在

点分治适合处理大规模的树上路径信息问题。

我们先随意选择一个节点作为根节点Root

所有完全位于其子树中的路径可以分为两种

一种是经过当前根节点的路径

一种是不经过当前根节点的路径

对于经过当前根节点的路径,又可以分为两种

一种是以根节点为一个端点的路径

一种是两个端点都不为根节点的路径

而后者又可以由两条属于前者链合并得到

所以,对于枚举的根节点Root,我们先计算在其子树中且经过该节点的路径对答案的贡献,再递归其子树对不经过该节点的路径进行求解

在本题中,对于经过根节点Root的路径,先枚举其所有子节点Ch,以Ch为根计算Ch子树中所有节点到Roto的距离。记节点i到当前根节点Root的距离为Dist(i)。

tf(d)表示之前处理过的子树中是否存在一个节点v使得Dist(v)=d。

若一个询问的k满足tf(k-Dist(i)) = true,则存在一条长度为k的路径。

在计算完Ch子树中所连的边能否成为答案后,我们将这些新的距离加入tf数组中。

注意在清空tf数组的时候不能直接用Memset,而应将之前占用过的Tf位置加入一个队列中,进行清空。这样才能保证时间复杂度。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
const int inf=2e9;
int n,m;
int q[maxn];
int rt;
int size[maxn];
int Max[maxn];
int dist[maxn];

bool tf[maxn*100];
bool ret[maxn];
bool vis[maxn];

int tot;
struct node {
    int u,v,w,nxt;
}edge[maxn<<1];
int head[maxn];
void addedge (int u,int v,int w) {
    edge[tot].u=u;
    edge[tot].v=v;
    edge[tot].w=w;
    edge[tot].nxt=head[u];
    head[u]=tot++;
    
    edge[tot].u=v;
    edge[tot].v=u;
    edge[tot].w=w;
    edge[tot].nxt=head[v];
    head[v]=tot++;
} 

int sum;
void cal_size (int u,int pre) {
    //计算子树大小
    //计算每个节点的最大子树大小
    size[u]=1;
    Max[u]=0;
    for (int i=head[u];i!=-1;i=edge[i].nxt) {
        int v=edge[i].v;
        if (v==pre) continue;
        if (vis[v]) continue;
        cal_size(v,u);
        Max[u]=max(Max[u],size[v]);
        size[u]+=size[v];
    } 
    Max[u]=max(Max[u],sum-size[u]);
    if (Max[u]<Max[rt]) rt=u; 
}

int dd[maxn];
int cnt;
void cal_dist (int u,int pre) {
    //计算路径
    dd[++cnt]=dist[u];//把每个点距离根节点的路径存到dd里 
    for (int i=head[u];i!=-1;i=edge[i].nxt) {
        int v=edge[i].v;
        if (v==pre) continue;
        if (vis[v]) continue;
        dist[v]=dist[u]+edge[i].w;
        cal_dist(v,u);
    } 
}

queue<int> tag;
void Divid (int u,int pre) {
    //分治
    tf[0]=true;
    tag.push(0);
    vis[u]=true;//u已经被分治
    for (int i=head[u];i!=-1;i=edge[i].nxt) {
        int v=edge[i].v;
        if (v==pre) continue;
        if (vis[v]) continue;
        dist[v]=edge[i].w;//v到根节点的路径为w
        cal_dist(v,u);//计算以v为根节点的子树的所有节点到u的距离
        for (int k=1;k<=cnt;k++) for (int j=1;j<=m;j++) {
            if (q[j]>=dd[k]) {
                //枚举已经记录的所有路径
                //如果第q次询问大于等于dd[k]
                //第q次询问的答案或上tf[q[j]-dd[k]]
                //tf数组表示长度为i的路径是否存在 
                ret[j]|=tf[q[j]-dd[k]];
            }
        } 
        for (int k=1;k<=cnt;k++) if (dd[k]<maxn*100) {
            tag.push(dd[k]);
            tf[dd[k]]=true;
        }
        cnt=0;
    } 
    while (tag.size()) tf[tag.front()]=false,tag.pop();
    for (int i=head[u];i!=-1;i=edge[i].nxt) {
        int v=edge[i].v;
        if (v==pre) continue;
        if (vis[v]) continue;
        sum=size[v];
        rt=0;
        Max[rt]=inf;
        cal_size(v,u);//先找到以u为根节点的子树的重心 
        cal_size(rt,-1);//以重心为根重新计算子树大小 
        Divid(rt,u);//对u这颗子树进行分治 
    } 
}

int main () {
    scanf("%d%d",&n,&m);
    for (int i=0;i<=n;i++) head[i]=-1;
    for (int i=1;i<n;i++) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    for (int i=1;i<=m;i++) scanf("%d",q+i);
    rt=0;
    Max[rt]=inf;
    sum=n;
    cal_size(1,-1);
    cal_size(rt,-1);
    Divid(rt,-1);
    for (int i=1;i<=m;i++) {
        if (ret[i])
            printf("AYE\n");
        else
            printf("NAY\n");
    }
}

 

posted @ 2020-10-23 12:09  zlc0405  阅读(82)  评论(0编辑  收藏  举报