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(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;
}