【点分治】BZOJ 1468:Tree

BZOJ 1468:Tree


Description

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K


Input

N 接下来n-1行边描述管道,按照题目中写的输入 接下来是k


Output

一行,有多少对点之间的距离小于等于k


Sample Input

7
1 6 13
6 3 9
3 5 7
4 1 3
2 4 20
4 7 2
10


Sample Output

5


HINT

n<=40000


Solution

每次把root为根所有子树节点的距离dis算出来
然后答案就是把所有子树节点的距离dis排序然后cal得到ans1
然后每个子树所有点再分别cal一次
以root为根的答案就是ans1-ans2
把所有这些加起来即可
吓死我了开始看了一遍查出好多错...果然模板以后还是自己打比较好

Code

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

#define maxn 400001

using namespace std;

struct edge{
    int to,lst,c;
}e[maxn*2];

int last[maxn],tot,son[maxn],n,f[maxn],root,d[maxn],deep[maxn],K,sum;

bool vis[maxn];

void getroot(int poi,int lst)
{
    son[poi]=1;f[poi]=0; 
    for(int i=last[poi];i;i=e[i].lst)
        if(e[i].to!=lst && !vis[e[i].to])
        {
            getroot(e[i].to,poi);
            son[poi]+=son[e[i].to];
            f[poi]=max(f[poi],son[e[i].to]);
        }
    f[poi]=max(f[poi],n-son[poi]);
    if(f[root]>f[poi])root=poi;
} 

void getdeep(int poi,int lst)
{
    deep[++deep[0]]=d[poi];
    for(int i=last[poi];i;i=e[i].lst)
        if(e[i].to!=lst && !vis[e[i].to])
        {
            d[e[i].to]=d[poi]+e[i].c;
            getdeep(e[i].to,poi);
        }
}

int getsum(int poi,int now)
{
    d[poi]=now;deep[0]=0;
    getdeep(poi,0);
    sort(1+deep,1+deep+deep[0]);
    int t=0,l,r;
    for(int l=1,r=deep[0];l<r;)
    {
        if(deep[l]+deep[r]<=K)t+=r-l,l++;
        else r--;
    }
    return t;
}

void solve(int poi)
{
    vis[poi]=1;
    sum+=getsum(poi,0);
    for(int i=last[poi];i;i=e[i].lst)
        if(!vis[e[i].to])
        {
            sum-=getsum(e[i].to,e[i].c);
            n=son[e[i].to];
            root=0;
            getroot(e[i].to,poi);
            solve(root);
        }
}

void add(int u,int v,int c)
{
    e[++tot]=(edge){v,last[u],c};last[u]=tot;
    e[++tot]=(edge){u,last[v],c};last[v]=tot;
} 

int main() 
{
    int k,m;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        add(u,v,c);
    }
    f[root]=100000;
    scanf("%d",&K);
    getroot(1,0);
    solve(root);
    printf("%d",sum);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值