题意:
给定n个点的树, K值
下面n-1条边
问 两点之间距离<= K的点对有多少
采用点分治,无根树转有根树时 根为树的重心(可以把树高度降低,防止树退化成链)
思路:
对于一棵 以u为根的树
以下我们成(a,b)为合法点对(即dist(a,b) <=K)
(a, b) 之间路径是唯一确定的。
将点对分2类:
1、两点间路径经过 u 点
2、两点间路径不经过u点 = 两点都在u的同一子树下 (也就是 a,b 有个公共祖先 x ,x是u的儿子节点 )
显然合法点对数 = 第一类+第二类
对于第一类://计算树的重心复杂度为 O(n) ,排序为O(nlogn)
经过u点 点对数 = 所有 dist(a,b) <=K 数 - 求和(u 的儿子节点v )(v子树中的合法节点数)
对于所有的 合法节点数 ,我们可以用单调性求出(具体实现在函数 getans() 中)
公式在work函数中实现。
这样计算完后,u节点就直接删去(用vis数组记录)
这样会得到u的儿子形成的森林
对于第二类://递归实现,由于用树的重心优化高度,递归次数为 log n
ans += u的所有儿子作为第一类点时的答案
总的复杂度为 O(n*logn * logn)
注意一点: 计算树的重心时,树的总节点数会改变(用size表示当前的节点数)
#pragma comment(linker, "/STACK:10240000000000,10240000000000")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
inline int Max(int a,int b){return a>b?a:b;}
inline int Min(int a,int b){return a<b?a:b;}
#define N 10100
struct Edge{int to, nex, dis;}edge[N<<1];
int head[N], edgenum;
void addedge(int u, int v, int dis){Edge E = {v, head[u], dis};edge[ edgenum ] = E;head[u] = edgenum++;}
int n, K, Ans;
int num[N];//num[i]表示 以i为根的树 节点数
int dp[N];//树重心的定义:dp[i]表示 将i点删去后 最大联通块的点数
int Stack[N], top1, root;
bool vis[N];//vis[i]表示i点是否删去
int size; //** 表示当前 计算的树的节点数
void getroot(int u, int fa){//找树的重心
dp[u] = 0; num[u] = 1; //以u为根的子树节点数
for(int i = head[u]; ~i; i = edge[i].nex){
int v = edge[i].to;
if(v != fa && !vis[v]){
getroot(v, u);
num[u] += num[v];
dp[u] = Max(num[v], dp[u]);
}
}
dp[u] = Max(dp[u], size - num[u]);
if(dp[u] < dp[root]) root = u;
}
int dis[N];
inline bool cmp(int i, int j){return dis[i] < dis[j];}
void Find_dis(int u, int fa, int d){ //把该树所有点入栈 并算出每个点到根节点的距离
Stack[++top1] = u;
dis[u] = d;
for(int i = head[u]; ~i; i =edge[i].nex){
int v = edge[i].to; if(vis[v] || v == fa)continue;
Find_dis(v, u, d+edge[i].dis);
}
}
int getans(int l, int r){//计算树中 距离<=k 的点对数量
int j = r, ans = 0;
for(int i = l; i <= r; i++){
while(dis[ Stack[i] ] + dis[ Stack[j] ] > K && j>i)j--;
if(i == j)return ans;
ans += j - i;
}
return ans;
}
//先找到重心 (这样转成有根树不会造成树退化的情况) 对于此树计算后,删去根节点(vis数组记录, 因为已经求出所有经过根节点的合法点对,则根节点无用了)
//若合法点对存在于子树中 则要递归计算子树的点对(用f数组判断点对是否在一个子树中) 将子树的重心来转为有根树进行操作
void work(int u, int fa){//计算出 路径经过u点的合法点对
//路径经过u点的合法点对数 = 总点对数 - 不经过u点(即点对都在一棵子树上)
dp[0] = N; size = num[u];//注意初始化
root = 0;
getroot(u, fa);//root 为u的联通块中的重心
top1 = 0; int top2 = 0;
for(int i = head[root]; ~i; i =edge[i].nex){
int v = edge[i].to; if(vis[v]) continue;
top2 = top1; //Stack[top2+1] 到 Stack[top1] 之间的点就是v子树所有的点
Find_dis(v, root, edge[i].dis);
sort(Stack + top2 +1, Stack + top1 +1 , cmp);
Ans -= getans(top2+1, top1);
}
//Stack[1] - Stack[top1]就是root子树所有的点
Stack[ ++top1 ] = root; dis[root] = 0;
sort(Stack + 1, Stack + top1 +1, cmp);
Ans += getans(1, top1);
vis[root] = 1;//去掉root点
for(int i = head[root]; ~i; i = edge[i].nex)//路径不经过u点的点对数
if(!vis[ edge[i].to]) work(edge[i].to, root);
}
void init(){
memset(head, -1, sizeof(head)); edgenum = 0;
memset(vis, 0, sizeof(vis)); Ans = 0;
}
int main(){
while(scanf("%d %d", &n, &K), n+K){
init();
for(int i = 1; i < n; i++)
{
int u, v, d; scanf("%d %d %d", &u, &v, &d);
addedge(u, v, d); addedge(v, u, d);
}
num[1] = n;
work(1, 0);
printf("%d\n",Ans);
}
return 0;
}