[学习笔记] 第九课:点分治和点分树(动态点分治)

前言

点分治一般是用来解决树上路径统计的问题, 而动态点分治(也称点分树),是用数据结构树上路径信息。

点分治

例题1 给出一颗树,求出所有边权和\(\leq k\)路径数量。

首先显然的\(\Theta (N^2 \log N)\)的枚举算法

for (int i = 1; i <= n; i ++ ) {
	for (int j = 1; j <= n; j ++ ) {
		if (dis (i, j) <= k) cnt ++ ;
	}
}

比较难优化,我们可以换一种考虑方式。
\(p\)为树根, 则对\(p\)而言, 树上的路径可以分为两种
1.经过\(p\)的路径
2.不经过\(p\)的路径(包含于\(p\)的某个子树中)
\(2\)种情况我们可以递归(以子树的根再进行计算)

void solve (int u) {
	vis[u] = 1; get_dis(u); ans += calc (d, 1);
	for (int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (vis[v]) continue; 
		dis[v] = 1; get_dis (v); ans -= calc (d, -1);//减去子树内重复路径
		solve (v);
	}
}

这样最劣时间复杂度仍为\(\Theta(N^2 \log N)\) 链既可以卡掉
如果递归到\(T\)层,那么时间复杂度为\(\Theta (TN\log N)\)
如何让\(T\)尽可能的小,我们可以每次选择树的重心作为根节点\(p\),容易证明点分治至多递归\(\log N\)层,时间复杂度也优化到了\(\Theta (N \log ^ 2N)\)

总而言之

点分治的过程为\(4\)
1.选当前子树的重心\(p\)作为根节点
2.从\(p\)出发进行一次\(dfs\),求出对应信息
3.执行\(solve(p)\)
4.标记\(p\),递归执行\(p\)的每棵子树

代码 Code

例题2 求树上乘积和为\(k\)的路径数

逆元预处理即可

例题3 给定一棵\(n\)个点的树,树上有\(m\)个黑点,求出一条路径,使得这条路径经过的黑点数\(\leq k\),且路径长度最大

用树状数组维护信息(细节:树状数组不能出现\(0\),所以集体向右偏转\(1\))

点分树(动态点分治)

例题4 给定一棵\(N\)个点的树, 每个点都有对应点权\(w_u\),我么有两个操作,要么修改\(x\)点权为\(y\),要么查询距离\(x\)点距离\(\leq K\)的点权和。

首先我们回忆一下点分治的过程,对于当前重心,他管辖的是经过他的路径,那么我们考虑修改一个点,那么有至多有\(\log N\)个重心路径受影响,我们可以用数据结构来维护相关信息,即可在\(\log ^2 N\)的时间解决。

对于每一个点,我们可以开一颗权值树状数组\(/\)权值线段树来维护,由于空间开不下所以常常要用动态开点线段树。维护经过当前点距离为\(s\)的点权之和。

我们可以在处理重心的时候检出一个点分树,及两两重心相连(不用建出来,只要建立上一层的重心即可)。

inline void insert (int rt, int id, int u, int f) {
	modify (root[rt][id], 0, n, dis[u], w[u]);
	for (int i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (v == f || vis[v]) continue;
		dis[v] = dis[u] + 1;
		insert (rt, id, v, u);
	}
}
inline void solve (int u) {
	vis[u] = 1; dep[u] = 0; insert (u, 0, u, 0);
	for (int i = head[u]; i; i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (vis[v]) continue;
		S = sz[v]; center = 0; find (v, u); fa[center] = u;
		dep[v] = 1; insert (center, 1, v, u);
		solve (center); 
	}
}

我们查询距离\(u\)点距离\(\leq K\)的路径

ans += query (rt[u], 0, n, K);
for (int i = u; fa[i]; i = fa[i]) {
	int dis = dist (u, fa[i]);
	ans += query (rt[fa[i]], 0, n, K - dis);
}

但这样我们发现有问题,因为有些路径重复算了,我们要减去子树的影响。

ans += query (rt[u][0], 0, n, K);
for (int i = u; fa[i]; i = fa[i]) {
	int dis = dist (u, fa[i]);
	ans += query (rt[fa[i][0], 0, n, K - dis);
	ans -= query (rt[i][1], 0, n, K - dis);
}
return ans;

同理修改也可以得出。
代码 Code

posted @ 2020-02-07 15:47  Hock  阅读(238)  评论(0编辑  收藏  举报