点分治学习笔记

点分治是一种处理树上路径相关问题的好方法。

先来一道题:洛谷 P3806 【模板】点分治1

暴力枚举显然是是 \(O(n^2)\),考虑使用点分治。

对于任意两点的路径,显然只有两种:

  1. 经过根结点 \(root\)
  2. 不经过根结点 \(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;
}

posted @ 2022-07-26 19:01  zltzlt  阅读(24)  评论(0编辑  收藏  举报