树的点分治
树上的点分治
复杂度 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
*/