树上点分治入门

分治就是分而治之。额……好像这话已经被说烂了。

学树分治之前我们先来回顾下线性分治。一般的线性分治都是将大的区间分成多个小的区间(一般是两个),然后将已处理好的小区间合并从而得到了这个大区间的结果,为了使分的次数尽可能少,应每次使分的小区间的大小尽可能平均,这样分的次数就会是log级别的。

好,现在我们要分树,参考线性分治的方法,每次选一个点,使删除这个点后被分开的连通块大小尽可能平均,暂且把这个点称为“中心点”

等等我们要先了解树分治是解决哪类问题的。个人认为:树分治一般用来处理树上的“全部路径”问题。故名思意“全部路径”就是树上所有点对之间的路径,看似这样的路径毫无规律,其实对于树上某一点来说,我们可以的将这些路径分为:通过这点的和永不通过这点的,其实不通过这点的路径一定通过其他某一个点。所以我们只需要求通过一个点的所有路径,求完后将这点彻底删除,然后同样的方法求其他点,直到遍历完所有的点为止。(时机已成熟,看来是时候该引入点分了)还记得上面的“中心点”吧,我们如果一个一个点没有一定顺序的去处理虽然可以得到正确答案,但是太慢了,如果每次递归处理“中心点”复杂度将减少很多很多,因为这样每次处理一个“中心点”的时,连通块的大小都是次数级减小的(多画几颗树体会一下就知道了)。

总结一下树上点分治的步骤:

1:找中心点

2:处理该中心点所在的连通块中的通过该中心点的路径的答案(比较拗口,仔细琢磨下)

3:删除该中心点,递归处理与该点相邻的连通块

具体实现看代码吧,入门题 

AC代码(老哥加油哦):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=10005;
struct Edge
{
    int a,b,len,pre;
}edge[N*2];
int vis[N],pre[N],siz[N],aa[N];
int sum,root,root_siz,edge_num,num,k,ans;
char ch;

inline int read()///读入挂
{
    int res=0;
    while(ch=getchar(),ch>'9'||ch<'0');
    res=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')res=10*res+ch-'0';
    return res;
}

inline void addedge(int a,int b,int len)///加边
{
    edge[++edge_num]=(Edge){a,b,len,pre[a]};
    pre[a]=edge_num;
}

void dfssum(int u,int f)///求连通块大小
{
    sum++;
    for(int i=pre[u];i;i=edge[i].pre)
    {
        int v=edge[i].b;
        if(vis[v]||v==f)continue;
        dfssum(v,u);
    }
}

void dfsroot(int u,int f)///求中心点
{
    siz[u]=1;
    int mx=0;
    for(int i=pre[u];i;i=edge[i].pre)
    {
        int v=edge[i].b;
        if(vis[v]||v==f)continue;
        dfsroot(v,u);
        siz[u]+=siz[v];
        mx=max(siz[v],mx);
    }
    mx=max(mx,sum-siz[u]);
    if(mx<root_siz)root=u,root_siz=mx;
}

void dfs(int u,int f,int d)///求该连通块中所有点到这点的距离
{
    aa[++num]=d;
    for(int i=pre[u];i;i=edge[i].pre)
    {
        int v=edge[i].b;
        if(vis[v]||v==f)continue;
        dfs(v,u,d+edge[i].len);
    }
}

int jisuan(int u,int add)///算该连通块中满足条件或不满足条件的路径数量
{
    num=0;
    dfs(u,0,add);
    sort(aa+1,aa+1+num);
    int ans_num=0,l=1,r=num;
    while(l<r)
    {
        while(l<r&&aa[l]+aa[r]>k)r--;
        if(l==r)break;
        ans_num+=r-l;
        l++;
    }
    return ans_num;
}

void chuli(int u)///将n个点按中心点顺序依次求对答案的贡献值
{
    sum=0;
    dfssum(u,0);
    root_siz=2e9;
    dfsroot(u,0);
    ans+=jisuan(root,0);
    ///printf("#%d %d %d %d\n",root,u,sum,root_siz);
    vis[root]=1;
    for(int i=pre[root];i;i=edge[i].pre)
    {
        int v=edge[i].b;
        if(vis[v])continue;
        ans-=jisuan(v,edge[i].len);
        chuli(v);
    }
}

int main()
{
    int n;
    while(1)
    {
        n=read(),k=read();
        if(n+k==0)break;
        for(int i=1;i<=n;i++)
            pre[i]=0,vis[i]=0;
        int a,b,len;
        edge_num=0;
        for(int i=1;i<n;i++)
        {
            a=read(),b=read(),len=read();
            addedge(a,b,len);
            addedge(b,a,len);
        }
        ans=0;
        chuli(1);
        printf("%d\n",ans);
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值