poj1987 树的点分治

题意:给你一颗n(n<=40000)的树,求两个点的距离<=k的点的对数

题解:

关于树的分治,这我还是第一次遇到,决定好好总结下,以备以后再遇到这种类型的题

分治要分的均匀,而要分治一颗树,最好的位置自然是树的重心,由重心开始,分治子树


这道题,对于每颗子树,点对之间的距离分为两种 1.过根节点 2.不过根节点

很显然,第二种情况可以作为子情况讨论。在计算一颗树的过根节点的点对数

是,还要减去其两个点在同一子树中的情况,避免重复计算


code:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

const int MAXN=40005;
const int inf=0x3f3f3f3f;
using namespace std;
struct node{
    int e,next,v;
}h[MAXN<<1];
int n,m,cnt,fir[MAXN];
int num[MAXN],f[MAXN];
int root,allnode,d[MAXN];
int deep[MAXN],ans,K;
bool vis[MAXN];

inline void addedge(int t1,int t2,int v){
    h[++cnt].e=t2;
    h[cnt].next=fir[t1];
    fir[t1]=cnt;
    h[cnt].v=v;
}
void Getroot(int s,int fa){
    num[s]=1; f[s]=0;
    for(int i=fir[s];i;i=h[i].next)
    {
        int e=h[i].e;
        if(e==fa||vis[e]) continue;
        Getroot(e,s);
        num[s]+=num[e];
        f[s]=max(f[s],num[e]);
    }
    f[s]=max(f[s],allnode-num[s]);
    if(f[s]<f[root]) root=s;
}
void Getdeep(int s,int fa){
    if(d[s]<=K) deep[++deep[0]]=d[s];
    for(int i=fir[s];i;i=h[i].next)
    {
        int e=h[i].e;
        if(e==fa||vis[e]) continue;
        d[e]=d[s]+h[i].v;
        Getdeep(e,s);
    }
}

int cal(int s,int now){//返回以s为根的子树中,当前深度为now有多少对点在K之内
    d[s]=now; deep[0]=0;
    Getdeep(s,0); int all=0;
    sort(deep+1,deep+deep[0]+1);
    //这里其中一个从小到大枚举,一个从大到小枚举,
    //找到第一个r使得当前l成立,那么对于l,在l,r的所有点对都满足
    //始终保证l<r,以免重复枚举
    //这里也包含了枚举的技巧,巧妙地用到了之前求出的值
    //如果之前的l,r不满足,那么l+1,r也一定不满足,满足只可能是l,r-1
    //如果之前的l,r满足,那么l+1,对于不满足l,r的点,自然也不满足
    for(int l=1,r=deep[0];l<r;)
    {
        if(deep[l]+deep[r]<=K){all+=r-l;l++;}
        else r--;
    }
    return all;
}
void solve(int s){
    ans+=cal(s,0); vis[s]=1;
    for(int i=fir[s];i;i=h[i].next)
    {
        int e=h[i].e;
        if(vis[e]) continue;
        ans-=cal(e,h[i].v);//减去在同一颗子树中的点对,避免重复计算
        allnode=num[e];
        root=0; Getroot(e,s);//分治
        solve(root);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int s,e,v; char t[5];
        scanf("%d%d%d%s",&s,&e,&v,t);
        addedge(s,e,v); addedge(e,s,v);
    }
    scanf("%d",&K);
    allnode=n; f[0]=inf;
    Getroot(1,0); solve(root);//找到重心,从重心处分治
    printf("%d\n",ans);
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值