POJ 1741 Tree (树分治)

题意:

一颗有边权的树,问有多少对点距离和小于k

思路:

树分治
我们取一颗树的重心,算出所有点到这个点的距离,然后我们就能通过排序去o(n)地找出有多少对是满足的,但是这样很明显有的对是不正确的,就是这两个点的lca并不是当前重心的时候
我们可以想到,当且仅当两点在同一以重心儿子为根的子树中,才能发生错误,我们就可以单独求一次某个子树的不符合数量即可
然后我们删去当前重心,继续去看拆掉重心后的子树,然后重复之前的步骤即可
复杂度是 O(nlognlogn)

错误及反思:

t了好多发,主要是没注意到maxn应该是每次dfs的时候都应该重新算,不重新算的话重心是错的
另外树分治的复杂度是可以保证的,每次取重心,所以最多logn次,每次遍历全部点,所以总体复杂度是nlogn

代码:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 10100;
struct EDGE{
    int to,val,next;
}e[N*2];
int first[N],n,k,tot,si[N],maxn[N];
bool did[N];
vector<int> len;
void addedge(int x,int y,int z){
    e[tot].to=y;
    e[tot].val=z;
    e[tot].next=first[x];
    first[x]=tot++;
    e[tot].to=x;
    e[tot].val=z;
    e[tot].next=first[y];
    first[y]=tot++;
}
void dfs1(int now,int fa){
    si[now]=1;
    maxn[now]=0;
    for(int i=first[now];i!=-1;i=e[i].next)
        if(e[i].to!=fa&&!did[e[i].to]){
            dfs1(e[i].to,now);
            si[now]+=si[e[i].to];
            maxn[now]=max(maxn[now],si[e[i].to]);
        }
}
void dfs3(int now,int fa,int& root,int& num,int t){
    int MA=max(maxn[now],si[t]-si[now]);
    if(MA<num){
        num=MA;
        root=now;
    }
    for(int i=first[now];i!=-1;i=e[i].next)
        if(e[i].to!=fa&&!did[e[i].to])
            dfs3(e[i].to,now,root,num,t);
}
void dfs2(int now,int fa,int tlen){
    for(int i=first[now];i!=-1;i=e[i].next)
        if(e[i].to!=fa&&!did[e[i].to])
            dfs2(e[i].to,now,tlen+e[i].val);
    len.push_back(tlen);
}
int cal(int v,int d){
    len.clear();
    dfs2(v,-1,d);
    sort(len.begin(),len.end());
    int l=0,r=len.size()-1,ans=0;
    while(l<r){
        if(len[l]+len[r]<=k) ans+=r-l,l++;
        else r--;
    }
    return ans;
}
int solve(int now){
    int root,num=1e9;
    dfs1(now,-1);
    dfs3(now,-1,root,num,now);
    int ans=cal(root,0);
    did[root]=true;
    for(int i=first[root];i!=-1;i=e[i].next)
        if(!did[e[i].to]){
            ans-=cal(e[i].to,e[i].val);
            ans+=solve(e[i].to);
        }
    return ans;
}
void init(){
    tot=0;
    memset(first,-1,sizeof(first));
    memset(did,false,sizeof(did));
}
int main(){
    while(scanf("%d%d",&n,&k)&&n){
        init();
        for(int i=0,u,v,w;i<n-1;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        printf("%d\n",solve(1));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值