链接
题意
给出一棵树,n个节点(n <= 10000),边含权,问有多少对点对之间的距离不超过k。
题解
楼教主男人八题之一,貌似是点分治的入门题。
这里大致介绍下点分治的过程:
实际上有个很早就接触过的分治算法叫“归并排序”,貌似是从那时候我才对分分治和nlogn算法有一些体会,归并排序的每层都是O(n),重点在于层数很少,只有logn层,因为每次都将区间分成原来的一半。
树上的点分治其实也差不多,重点在于怎么将树形结构分成尽可能相等的两部分,这就要求找到树的重心,然后向下递归即可,基本上就是树上的归并。
树的重心事实上不一定能将树分成尽可能相等的两部分(比如一个有三条相等的链的树),但是这样并不会增加时间复杂度(相当于logk(n)对不同k的杂糅)。
最后就是分治算法的应用场景,应该需要多做点题好好体会体会。
代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn (10010)
struct node
{
int v, l;
node() { }
node(int _v, int _l) : v(_v), l(_l) { }
};
vector<node> g[maxn];
int n, size, s[maxn], f[maxn], root, d[maxn], K, ans, done[maxn];
vector<int> dep;
void getroot(int u, int fa)
{
s[u] = 1, f[u] = 0;
for(int i = 0, v; i < g[u].size(); i++)
if((v = g[u][i].v) != fa && !done[v]) {
getroot(v, u);
s[u] += s[v];
f[u] = max(f[u], s[v]);
}
f[u] = max(f[u], size - s[u]);
if(f[u] < f[root]) root = u;
}
void getdep(int u, int fa)
{
s[u] = 1;
dep.push_back(d[u]);
for(int i = 0, v; i < g[u].size(); i++)
if((v = g[u][i].v) != fa && !done[v]) {
d[v] = d[u] + g[u][i].l;
getdep(v, u);
s[u] += s[v];
}
}
int calc(int u, int init)
{
dep.clear(); d[u] = init;
getdep(u, 0);
sort(dep.begin(), dep.end());
int ret = 0;
for(int l = 0, r = dep.size() - 1; l < r; )
{
if(dep[l] + dep[r] <= K) ret += r - l++;
else r--;
}
return ret;
}
void work(int u)
{
ans += calc(u, 0);
done[u] = 1;
for(int i = 0, v; i < g[u].size(); i++)
if(!done[v = g[u][i].v]) {
ans -= calc(v, g[u][i].l);
f[0] = size = s[u];
getroot(v, root = 0);
work(root);
}
}
int main()
{
while(cin >> n >> K)
{
if(!n && !K) break;
for(int i = 0; i <= n; i++)
{ g[i].clear(); done[i] = 0; }
for(int i = 0, u, v, l; i < n-1; i++)
{
scanf("%d%d%d", &u, &v, &l);
g[u].push_back(node(v, l));
g[v].push_back(node(u, l));
}
f[0] = size = n;
getroot(1, root = 0);
ans = 0;
work(root);
printf("%d\n", ans);
}
return 0;
}