树的点分治

本文介绍了一种解决POJ1741问题的方法,该问题要求计算一棵树中任意两点间距离不超过给定值k的节点对数量。采用树的重心分解技巧,并通过递归计算每棵子树的贡献,最终实现高效求解。

poj 1741
题意:给定n个点的一棵树,还有一个k值,问树上任意两节点之间距离小于等于k的有多少对。
思路:先将无根树转化为有根树,可以统计出来每个点到达根的距离,然后就可以将所有的节点深度排序,可以O(n)计算出满足题意的有多少种。
但是这样包含在同一棵子树中的两个节点的贡献值,而且还漏掉了在同一颗子树的两个节点的贡献,因为这样算的是通过根的距离,所以我们就可以将所有的情况都计算出来,然后减去根所有的子树的所贡献出来的情况,这样就是只通过根的情况数了,然后再递归下去算出子树的所有情况加起来就好了,但是有可能树是一条链,所以每一次计算之前,计算一下节点所在树块的重心,然后从重心开始计算,然后重心就可以将当前树块分成很多大小相似的许多小块,最少两块,时间复杂度n*log(n)*log(n)。

#include<cstdio>//树的重心就是重心的子节点的最大节点数最小
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
int n,k,head[maxn],cont;
int vis[maxn],siz[maxn],root,f[maxn],ans,d[maxn],tot,deep[maxn],hehe,sn;
/*
sn代表的是当前处理的树块的节点数
siz代表以i为根的子树的节点数
vis标记点是否被用过,也可以理解为用来将树分块
deep代表节点的深度(边权)
*/
struct zp
{
    int u,v,w,next;
} node[maxn*2];
void init()
{
    cont=0;
    hehe=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
}
void add(int u,int v,int w)
{
    node[cont].u=u,node[cont].v=v;
    node[cont].w=w;
    node[cont].next=head[u];
    head[u]=cont++;
}
void get_root(int u,int fa)//求树的重心
{
    siz[u]=1;
    f[u]=0;
    for(int i=head[u]; i!=-1; i=node[i].next)
    {
        int v=node[i].v;
        if(!vis[v]&&v!=fa)
        {
            get_root(v,u);
            siz[u]+=siz[v];
            f[u]=max(f[u],siz[v]);
        }
    }
    f[u]=max(f[u],sn-siz[u]);//求出以u为根节点时子节点的最大节点数
    if(f[root]>f[u]) root=u;//更新重心
}
void get_deep(int u,int fa)//求出以i为根的书上同一个块到i节点的距离
{
    hehe++;
    d[tot++]=deep[u];
    for(int i=head[u]; i!=-1; i=node[i].next)
    {
        int v=node[i].v,w=node[i].w;
        if(!vis[v]&&v!=fa)
        {
            deep[v]=deep[u]+w;
            get_deep(v,u);
        }
    }
}
int calc(int u)//计算以u为重心的树块符合题意的节点对数
{
    tot=0;
    get_deep(u,-1);
    sort(d,d+tot);
    int i=0,j=tot-1,sum=0;
    while(i<j)
    {
        if(d[i]+d[j]<=k)
        {
            sum=sum+j-i;
            i++;
        }
        else j--;
    }
    return sum;
}
void dfs(int u)//树的分治
{
    deep[u]=0;
    vis[u]=1;//树的点分治标记后将原本的树块分成两块
    ans+=calc(u);
    for(int i=head[u]; i!=-1; i=node[i].next)
    {
        int v=node[i].v;
        if(!vis[v])
        {
            deep[v]=node[i].w;
            hehe=0;
            ans-=calc(v);
            sn=hehe;
            root=0;
            get_root(v,-1);
            dfs(root);
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&k)&&(n||k))
    {
        init();
        for(int i=1; i<n; i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        root=0;
        f[0]=0x3f3f3f3f;
        ans=0;
        sn=n;
        get_root(1,-1);
        dfs(root);
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值