POJ 1741 Tree (树的点分治简单题)

本文深入探讨了点分治算法在解决树上距离问题的应用,通过实例讲解如何计算树上节点对之间的距离不超过给定阈值的有效对数,介绍了算法的基本思想、关键步骤及其实现代码。

Tree

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

题意: 给一棵树,问有多少对 (u,v) 满足 u 和 v 的距离 <= k,其中 u,v 不含顺序

思路: 点分治简单题。

假设当前树的根为 rtrtrt,考虑两种可能:

  • u,vu,vuv 之间的路径不经过 rtrtrt
  • u,vu,vuv 之间的路径经过 rtrtrt

对于第二种情况,处理所有点到 rtrtrt 的距离 dis[],那么当一对 (u, v) 满足 dis[u]+dis[v]<=kdis[u] + dis[v] <= kdis[u]+dis[v]<=klca(u,v)=rtlca(u,v) = rtlca(u,v)=rt,那么这一对 (u,v) 就是合法的。

所以当只满足 dis[u]+dis[v]<=kdis[u] + dis[v] <= kdis[u]+dis[v]<=k 时,计算的结果会有不合法的答案。那么容斥一下,只需要以儿子为重心,计算子树的答案,然后减去,便得到了第二种情况的答案。而第一种,就是第二种的递归状态。

设当前根为 rtrtrt,满足 dis[u]+dis[v]<=kdis[u] + dis[v] <= kdis[u]+dis[v]<=k 的答案数为 cnt1,对 rtrtrt 的儿子,以儿子为重心,计算子树的答案,得儿子答案数的总和为 cnt2,那么经过 rtrtrt 满足条件的答案数就是 cnt1 - cnt2 .

这里我们还需要利用树的重心 ( 找到一个点,其所有的子树中最大的子树节点数最少 ) 降低复杂度。

每一次操作的 rtrtrt 都是当前树的重心,当 rt 计算完之后,就把这个点去掉,形成多棵树,然后递归至其儿子。 因为是树,这个去掉的操作就可以很简单实现,标记 vis[rt],然后搜索的时候不往回即可。

Code:

#include "cstdio"
#include "vector"
#include "cstring"
#include "algorithm"
#define debug(x) cerr << "[" << #x <<": " << (x) <<"]"<< endl
#define pii pair<int,int>
#define clr(a,b) memset((a),b,sizeof(a))
#define rep(i,a,b) for(int i = a;i < b;i ++)
#define pb push_back
#define MP make_pair
#define LL long long
#define ull unsigned LL
#define ls i << 1
#define rs (i << 1) + 1
#define fi first
#define se second
#define ptch putchar
#define CLR(a) while(!(a).empty()) a.pop()

using namespace std;

#ifndef ONLINE_JUDGE
//clock_t prostart = clock();
#endif

const int maxn = 1e4 + 10;
const int inf = 0x3f3f3f3f;
int n,k;
struct xx{
    int u,v,w,nex;
}edge[maxn << 1];
vector<int>dis;
int head[maxn],cnt;
int Sonmax[maxn],Sonnum[maxn];
int vis[maxn];
int ans = 0;

void init(int n){
    for(int i = 0;i <= n + 5;++ i){
        head[i] = -1;
        vis[i] = 0;
    }
}

void Tree_Size(int p,int fa){             /// 处理当前树的大小
    Sonmax[p] = Sonnum[p] = 1;
    for(int i = head[p]; ~i;i = edge[i].nex){
        int v = edge[i].v;
        if(vis[v] || v == fa) continue;
        Tree_Size(v,p);
        Sonnum[p] += Sonnum[v];
        Sonmax[p] = max(Sonmax[p],Sonnum[v]);
    }
}

void Tree_center(int &minn,int &rt,int p,int fa,int sum){      /// 利用处理的树的大小找到重心
    for(int i = head[p]; ~i;i = edge[i].nex){
        int v = edge[i].v;
        if(vis[v] || v == fa) continue;
        Tree_center(minn,rt,v,p,sum);
    }
    if(minn > max(Sonmax[p],sum - Sonnum[p])){
        minn = max(Sonmax[p],sum - Sonnum[p]);
        rt = p;
    }
}

void Tree_dis(int p,int fa,int dep){        /// 计算当前树所有点到重心的距离
    dis.pb(dep);
    for(int i = head[p]; ~i;i = edge[i].nex){
        int v = edge[i].v;
        if(vis[v] || v == fa) continue;
        Tree_dis(v,p,dep + edge[i].w);
    }
}

int cal(int rt,int dep){        //rt 树重心,计算答案
    dis.clear();
    Tree_dis(rt,-1,dep);
    sort(dis.begin(),dis.end());
    int res = 0;
    int i = 0,j = dis.size() - 1;
    while(i < j){
        while(i < j && dis[i] + dis[j] > k) -- j;
        res += j - i;
        i ++;
    }
    return res;
}

void dfs(int p,int fa){
    int rt,minn = inf;
    Tree_Size(p,-1);
    Tree_center(minn,rt,p,-1,Sonnum[p]);
    ans += cal(rt,0);               /// 容斥
    vis[rt] = 1;                        /// 标记 rt ,表示分割  树
    for(int i = head[rt]; ~i;i = edge[i].nex){
        int v = edge[i].v;
        if(vis[v] || v == fa) continue;
        ans -= cal(v,edge[i].w);        /// 容斥
        dfs(v,rt);					 ///稍微注意一下,上一次访问的 rt
    }
}

int main() {
#ifndef ONLINE_JUDGE
//    freopen("in.txt", "r", stdin);
//    freopen("out.txt", "w", stdout);
#endif

    while(~scanf("%d%d",&n,&k)){
        if(!n && !k) break;
        init(n);
        ans = cnt = 0;
        for(int i = 1;i < n;++ i){
            int u,v,w; scanf("%d%d%d",&u,&v,&w);
            edge[cnt] = xx{u,v,w,head[u]};
            head[u] = cnt ++;
            edge[cnt] = xx{v,u,w,head[v]};
            head[v] = cnt ++;
        }
        dfs(1,-1);
        printf("%d\n",ans);
    }

#ifndef ONLINE_JUDGE
//    cerr << "time: " << 1.0 * (clock() - prostart) / CLOCKS_PER_SEC << " s" << endl;
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值