树上的点分治

树的点分治


树上的点分治
复杂度 logn
多用来处理一些树上的路径问题

树的重心:根据重心将一棵树分割后,得到的最大子树的顶点数最小。
依照重心划分树,每次划分的子树的大小不超过n/2
因此划分的复杂度为logn。
查找重心:

int vis[MAX_N];//重心标记
int siz[MAX_N], mson[MAX_N];//siz是子树的顶点大小,mson是最大子树的大小
//int dis[MAX_N]; //这个是距离数组,算路径时需要
int root, sum, tot, ans;//root用来更新重心
                        //sum是当前查询重心的子树的大小
                        //tot是表示距离数组的上界
void get_root(int u, int fa) { //v为当前节点(当前的根),fa为v的父亲, sn是查找重心的树的大小
    siz[u] = 1; mson[u] = 0;  //siz是以u为顶点的子树大小,mson是以u为分割点的最大子树
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == fa || vis[v]) continue;//回到父亲或者下一节点是重心标记过了,直接跳过
        get_root(v, u);  //递归
        siz[u] += siz[v]; //以u为根的子树大小
        mson[u] = max(mson[u], siz[v]); //求最大子树
    }
    mson[u] = max(mson[u], sum - siz[u]); //当前最大子树和若以u为顶点的子树顶点-当前最大子树, 相比较
    if (mson[u] < mson[root]) root = u;   //最大子树的顶点数最小的子树
}

//在main函数中,第一次查重心时
//先将root置为0,将mson[0] = INF, 方便查找时更新

POJ1741 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之间的距离小于k的点对数

思路:树的点分治
点对有两种(1)在同一子树中的点对, (2)在不同子树中的点对(此时必然经过重心)
第一种点对在不断递归后就是第二种点对
算第二种点对时,先求出子树中各点到重心的距离,再枚举(这里可以优化,排序后双向考虑,类似于尺取),但是这里在算的时候,必然会多算了在同一子树的点对,把这两点的距离当成了两点到重心的距离之和,但实际上可能会更短。
这个可以在重心分割子树后再减去子树中的点对数量
具体代码:

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define mod 1000000007
#define eps 1e-6
#define ll long long
#define INF 0x3f3f3f3f
#define MEM(x,y) memset(x,y,sizeof(x))
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int MAX_N = 10010;
int n, k, cnt;
struct edge{
    int u, v;
    int len;
    int next;
}es[2 *MAX_N];
int head[MAX_N];

int vis[MAX_N];//重心标记
int siz[MAX_N], mson[MAX_N];//siz是子树的顶点大小,mson是最大子树的大小
int dis[MAX_N];
int root, sum, tot, ans;

void add_edge(int u, int v, int l) {
    es[cnt].u = u;
    es[cnt].v = v;
    es[cnt].len = l;
    es[cnt].next = head[u];
    head[u] = cnt++;
}

void get_root(int u, int fa) { //v为当前节点(当前的根),fa为v的父亲, sn是查找重心的树的大小
    siz[u] = 1; mson[u] = 0;  //siz是以u为顶点的子树大小,mson是以u为分割点的最大子树
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == fa || vis[v]) continue;
        get_root(v, u);  //递归
        siz[u] += siz[v];
        mson[u] = max(mson[u], siz[v]); //求最大子树
    }
    mson[u] = max(mson[u], sum - siz[u]); //当前最大子树和若以u为顶点的子树顶点-当前最大子树, 相比较
    if (mson[u] < mson[root]) root = u;   //最大子树的顶点数最小的子树
}

void get_dis(int u, int d, int fa) { //求点到重心的距离
    dis[++tot] = d;
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == fa || vis[v]) continue;
        get_dis(v, d + es[i].len, u);
    }
}

int calc(int u, int d) {  //到u的距离为d
    int ret = 0;
    tot = 0;
    get_dis(u, d, 0);
    sort(dis + 1, dis + tot + 1);
    int i = 1, j = tot;
    while (i < j) {
        if (dis[i] + dis[j] <= k && i < j) {
            ret += (j - i);
            i++;
        }
        else j--;
    }
    return ret;
}

void solve(int u) { //u是重心
    ans += calc(u, 0); //以u为重心分割的两颗子树,计算两颗子树间的点的距离小于k的对数
    vis[u] = 1;        //但是这里计算时,必然会多加上两点在同一颗子树中的情况(这是不需要的)
                       //因此在后面重心剖分子树后,要减去该重心延伸的所有子树中的(距离<=k的)点对
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (vis[v]) continue;
        ans -= calc(v, es[i].len); //去掉子树中重复的
        sum = siz[v]; //以v为顶点的子树大小
        root = 0;     //以为mson[0] = INF, 这样方便计算
        get_root(v, u); //找到重心,然后继续递归
        solve(root);
    }
}
int main() {
    while(scanf("%d%d", &n, &k)) {
        if (n == 0 && k == 0) break;
        cnt = 0;
        ans = 0;
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        for (int i = 0, x, y, l; i < n - 1; i++) {
            scanf("%d%d%d", &x, &y, &l);
            add_edge(x, y, l);
            add_edge(y, x, l);
        }
        sum = n;  //取第一个重心前的子树大小,也就是原来树的大小
        root = 0;
        mson[0] = INF; //此时mson[root] = INF,方便后面比较
        get_root(1, 0); //得到第一个重心
        solve(root);
        printf("%d\n", ans);
    }
    return 0;
}
/*
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
*/
/*
8
*/


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值