POJ1741 Tree(点分治,带详解)

版权声明:欢迎转载~转载请注明出处! https://blog.csdn.net/riba2534/article/details/83031511

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

思路

本题是点分治的模板题

题意是给了你一棵树,然后给你一个 k k ,问你在这棵树上有多少对点之间的距离小于等于 k k

我们要用点分治来解决这道题。

我们可以先想一个小问题,如何求出有多少对经过根的路径且距离小于等于k的节点。我们可以把这一课树上的每一个点到树根的距离计算出来,然后从小到大排序,利用二分如果出现两个点深度的和小于等于k(deep[l]+deep[r]<=k),那么就有r-l个点符合要求,可以加到答案中去.

点分治就是在解决树上路径问题时,我们可以选取一点为根,将树转化为有根树,然后考虑经过根的所有路径(有时将两条从根出发的路径连接为一条)。统计完这些路径的答案后,将根节点标记为删除,对剩下的若干棵树进行同样的操作。

点分治的时候有一个重要的操作,就是求出树的重心,树的重心指的是以该点为根时这棵树的最大子树最小。求得时候我们可以定义:

  • siz[u]:以u为根的子树(包括自己)的节点数量
  • f[u]:以u为根的最大子树

这两个数组我们可以通过一遍dfs来求出,并且f[i]最大的点就是我们要找的树的重心.

之后我们就可以通过点分治来递归的解决这个问题,计算完一个根就把这个根删去,最后累加完结果.

但是,点分治要求处理的路径是经过root,所以如果一条路径是在同一个子树之内的就不符合要求,所以还要对子树dfs一下,然后去重

具体实现,详见代码.

参考博客:【点分治】的学习笔记和众多例题\

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
#define mem(a, b) memset(a, b, sizeof(a))
const int inf = 1e9 + 10;
const int N = 10010 + 10;
int root, n, k, ans, sum;
int siz[N], f[N]; //siz[i]表示以i为根的节点数量,f[i]表示以i为根的最大子树大小
int first[N], tot;
int vis[N]; //标记这个点有没有被删过
int d[N], deep[N];
struct edge
{
    int v, w, next;
} e[N * 2];
void add_edge(int u, int v, int w)
{
    e[tot].v = v, e[tot].w = w;
    e[tot].next = first[u];
    first[u] = tot++;
}
void getroot(int u, int fa)
{
    siz[u] = 1;
    f[u] = 0;
    for (int i = first[u]; ~i; i = e[i].next)
    {
        int v = e[i].v;
        if (v == fa || vis[v])
            continue;
        getroot(v, u);
        siz[u] += siz[v];
        f[u] = max(f[u], siz[v]);
    }
    f[u] = max(f[u], sum - siz[u]); //以u的父节点为根的子树
    if (f[u] < f[root])
        root = u;
}
void getdeep(int u, int fa)
{
    deep[++deep[0]] = d[u];
    for (int i = first[u]; ~i; i = e[i].next)
    {
        int v = e[i].v, w = e[i].w;
        if (v != fa && !vis[v])
        {
            d[v] = d[u] + w;
            getdeep(v, u);
        }
    }
}
int cal(int u, int cost)
{
    d[u] = cost;
    deep[0] = 0;                        //deep[0]表示深度
    getdeep(u, 0);                      //处理以u为根的树深度
    sort(deep + 1, deep + deep[0] + 1); //对所有的深度进行排序
    int l = 1, r = deep[0], res = 0;
    while (l < r)
    {
        if (deep[l] + deep[r] <= k) //判断是否符合条件.
        {
            res += r - l;
            l++;
        }
        else
            r--;
    }
    return res;
}

void solve(int u)
{
    ans += cal(u, 0); //处理以u点为根的树
    vis[u] = 1;
    for (int i = first[u]; ~i; i = e[i].next)
    {
        int v = e[i].v, w = e[i].w;
        if (!vis[v])
        {
            ans -= cal(v, w); //减去同一个子树内不满足要求的
            sum = siz[v];
            root = 0;
            getroot(v, 0);
            solve(root);
        }
    }
}
void init()
{
    ans = root = tot = 0;
    mem(first, -1);
    mem(vis, 0);
}
int main()
{
    //freopen("in.txt", "r", stdin);
    int u, v, w;
    while (scanf("%d%d", &n, &k) && (n || k))
    {
        init();
        for (int i = 1; i <= n - 1; i++)
        {
            scanf("%d%d%d", &u, &v, &w);
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
        f[0] = inf;
        sum = n;
        getroot(1, 0);
        solve(root);
        printf("%d\n", ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/riba2534/article/details/83031511