解题报告:POJ1741 Tree 树上点分治(经典好题)

本文详细介绍了如何利用树上点分治方法解决POJ1741 Tree问题,包括寻找树的重心、计算所有点到根的距离以及统计点距小于等于k的组数。通过分治策略,实现了NlogNlogN的复杂度解题方案。
摘要由CSDN通过智能技术生成
Tree
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 19522 Accepted: 6388

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

Source

[Submit]   [Go Back]   [Status]   [Discuss


题意:

给一棵树,求树上所有两点之间点距小于等于k的组数


思路:

比较综合的一道树上点分治,虽说分解问题后每一步都比较好想到,但是写起来还是没那么轻松。。


一. 找出当前树重心作为根

写的树上点分治不多,但是找重心是维护一个递归深度的常用方法,比较直接的做法就是从重心的定义(去掉重心,各个新生成的树的结点数 不超过 原来树的总结点数的一半)出发,找一个点判断去掉它之后是否满足要求,如果满足它就是重心了,一遍dfs可以解决,复杂度为 N 


二. 求出所有点的到根距离

这个比较容易,还是一遍dfs解决,复杂度为  N


三. 统计组数

分两个小部分

1.统计过根的组数

这个我的做法是在将(二)中所有点到根的距离存进数组,进行排序(NlogN),然后用双指针(N)就能得到,写的不是很熟练...

需要注意是因为过根,所以必然是不同子树,那么需要记录每个点属于哪个子树,统计 (总数目-同一子树下的数目)即可。

2.统计到根的组数

这个在(二)中进行dfs时直接统计就好





四. 递归子树

然后把根移出,递归它的子树即可


那么总复杂度就为 NlogNlogN,满足题目的要求


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
/**
1. 找出重心作为根
2. 求出所有点的到根距离
3. 统计组数
4. 递归子树
**/

struct edge{
    int t;
    int w;
    edge(){}
    edge(int tt,int ww){
        t = tt ; w = ww;
    }
};


int cnt,n,m ;
bool root[10005];
vector<edge>G[10005];
int has[10005];
int len[100005];
int F[100005];
int ID[10005];
int had[10005];

inline int get(int x,int f,const int& all,int &R){
    had[x]=0;
    has[x] = 1;
    int mx = 0;
    for(int i=0;i<G[x].size();i++){
        edge& j = G[x][i];
        if(!root[j.t]&&j.t!=f){
            has[x]+=get(j.t,x,all,R);
            mx = max(mx,has[j.t]);
        }
    }mx = max(mx,all-has[x]);
    if(mx<=(all>>1)){
        R = x;
    }return has[x];
}

inline void oper(int x,int f,const int& ff,int d,int &all){
    if(d>m)return;
    F[cnt]=ff;
    len[cnt]=d;
    ID[cnt]=cnt++;
    if(d<=m)all++;
    for(int i=0;i<G[x].size();i++){
        edge &j = G[x][i];
        if(!root[j.t]&&j.t!=f){
            oper(j.t,x,ff,d+j.w,all);
        }
    }
}

inline bool cmp(const int& x,const int& y){
    return len[x]<len[y];
}

inline int get_num(){
    int id = 0;
    sort(ID,ID+cnt,cmp);
    int res = 0;
    for(int i=cnt-1;i>0;i--){
        int &ii = ID[i];
        while(id<i&&len[ID[id]]+len[ii]<=m){
            had[F[ID[id++]]]++;
        }while(id>i){
            had[F[ID[--id]]]--;
        }
        res += id - had[F[ii]];
    }return res;
}

inline int work(int x,int num){
    int res = 0;
    int R;
    get(x,x,num,R);
    root[R]=true;
    cnt = 0;
    for(int i=0;i<G[R].size();i++){
        edge &j = G[R][i];
        if(!root[j.t]){
            oper( j.t , R , j.t , j.w , res );
        }
    }
    res += get_num();
    for(int i=0;i<G[R].size();i++){
        edge &j = G[R][i];
        if(!root[j.t]){
            res += work(j.t,has[j.t]<=(num>>1)?has[j.t]:num-has[R]);
        }
    }
    return res;
}

int main()
{
    while(scanf("%d%d",&n,&m)==2&&(n||m)){
        for(int i=0;i<10005;i++){
            G[i].clear();
            had[i]=has[i]=root[i]=false;
        }
        for(int i=1,s,t,w;i<n;i++){
            scanf("%d%d%d",&s,&t,&w);
            G[s].push_back(edge(t,w));
            G[t].push_back(edge(s,w));
        }printf("%d\n",work(1,n));
    }return 0;
}

/*
13 6
1 2 1
1 3 1
1 4 1
2 5 1
2 6 1
2 7 1
4 8 1
4 9 1
4 10 1
3 11 1
3 12 1
3 13 1

78
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值