点分治学习笔记
点分治是一种处理树上路径相关问题的好方法。
先来一道题:洛谷 P3806 【模板】点分治1
暴力枚举显然是是 \(O(n^2)\),考虑使用点分治。
对于任意两点的路径,显然只有两种:
- 经过根结点 \(root\)
- 不经过根结点 \(root\)
对于情况 \(1\) 的路径长度是很好算的,\(\mathrm{dist}(u, v) = \mathrm{dist}(u, root) + \mathrm{dist}(v, root)\),算一遍所有结点的深度即可。
对于情况 \(2\),可以继续递归下去,找到另一个 \(root\),将它转化为情况 \(1\) 来讨论。
这样递归下去,时间复杂度是和递归层数有关的。而我们需要选择合适的 \(root\),使得递归层数尽量小。
还记得 树的重心 吗?以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。那么我们每次递归就找子树的重心,以它为根递归下去即可。由于每次递归,当前根结点子树的大小都会减半,所以递归层数是 \(O(\log n)\) 级别的。
对于这道题,可以先将所有询问离线下来,每次递归就处理经过当前根结点的所有路径对询问的影响(Y / N)即可。
这样总时间复杂度就降到了 \(O(nm \log n)\)。
模板题代码
/*
p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 10050;
const int maxm = 10000100;
int n, m, qq[maxn], head[maxn], len;
int f[maxn], sz[maxn], root, da[maxn], tot;
int stk[maxn], top;
bool ans[maxn], mk[maxm], vis[maxn];
struct edge {
int to, dis, next;
} edges[maxn << 1];
void add_edge(int u, int v, int d) {
edges[++len].to = v;
edges[len].dis = d;
edges[len].next = head[u];
head[u] = len;
}
int dfs2(int u, int fa, int sn) {
f[u] = sz[u] = 1;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (v == fa || vis[v]) {
continue;
}
sz[u] += dfs2(v, u, sn);
f[u] = max(f[u], sz[v]);
}
f[u] = max(f[u], sn - sz[u]);
if (!root || f[u] < f[root]) {
root = u;
}
return sz[u];
}
void dfs3(int u, int fa, int dd) {
da[++tot] = dd;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to, d = edges[i].dis;
if (v == fa || vis[v]) {
continue;
}
dfs3(v, u, dd + d);
}
}
void calc(int u) {
top = 0;
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to, d = edges[i].dis;
if (vis[v]) {
continue;
}
tot = 0;
dfs3(v, u, d);
for (int j = 1; j <= tot; ++j) {
for (int k = 1; k <= m; ++k) {
if (qq[k] >= da[j]) {
ans[k] |= mk[qq[k] - da[j]];
}
}
}
for (int j = 1; j <= tot; ++j) {
if (da[j] <= 10000000) {
mk[da[j]] = 1;
stk[++top] = da[j];
}
}
}
while (top) {
mk[stk[top--]] = 0;
}
}
void dfs(int u) {
vis[u] = mk[0] = 1;
calc(u);
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (vis[v]) {
continue;
}
root = 0;
dfs2(v, u, sz[v]);
dfs(root);
}
}
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1, u, v, d; i < n; ++i) {
scanf("%d%d%d", &u, &v, &d);
add_edge(u, v, d);
add_edge(v, u, d);
}
for (int i = 1; i <= m; ++i) {
scanf("%d", &qq[i]);
}
dfs2(1, -1, n);
dfs2(root, -1, n);
dfs(root);
for (int i = 1; i <= m; ++i) {
puts(ans[i] ? "AYE" : "NAY");
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}