题意:
给定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; }