点分治总结

概念

点分治可以:

  • 处理树上路径问题(通常与“树上任意两点之间的路径”有关)(如树上距离、树上路径边数等)
  • 处理树上可二分型问题:使用点分治优化一步步走的过程。(如树的重心)

点分治通过不断找树的重心并删除,划分成若干个子树,在子树内再找重心继续递归。每个子树内分别求解答案。

复杂度为\(\mathcal{O}(n\log n)\)(还取决于\(calc\)函数的复杂度,\(calc\)函数的复杂度最大为\(\mathcal{O}(sz\log sz)\)

代码分析

注:以P4178 Tree为例(求出树上两点距离小于等于\(k\)的有序点对数量)

void ask_rt(int x, int fa) // 找到子树的重心
{
	sz[x] = 1;
	int mx = 0;
	for (int i = hd[x]; i; i = nxt[i])
	{
		int y = to[i];
		if (vis[y] || y == fa) continue; // 注意
		ask_rt(y, x);
		sz[x] += sz[y];
		mx = max(mx, sz[y]);
	}
	mx = max(mx, sum - sz[x]);
	if (mx < mn)
	{
		mn = mx;
		rt = x;
	}
	return;
}

void work(int x, int fa, int s) // 算出以当前子树重心为根x和子树内其他节点y之间的所有距离
{
	sta[ ++ top] = s;
	for (int i = hd[x]; i; i = nxt[i])
	{
		int y = to[i], z = w[i];
		if (vis[y] || y == fa) continue;
		work(y, x, s + z);
	}
	return;
}

int calc(int rt_, int s) // 找到当前子树内的答案
{
	top = 0;
	work(rt_, 0, s);
	sort(sta + 1, sta + top + 1);
	int cnt = 0;
	for (int i = 1; i < top; i ++ )
	{
		int pos = upper_bound(sta + i + 1, sta + top + 1, k - sta[i]) - sta;
		cnt += (pos - i - 1);
	}
	return cnt;
}

void part(int x) // 进行点分治
{
	vis[x] = 1; ask_rt(x, 0); // 重算一遍size
	res += calc(x, 0);
	for (int i = hd[x]; i; i = nxt[i])
	{
		int y = to[i], z = w[i];
		if (vis[y]) continue;
		res -= calc(y, z); // 去重,防止重复计算。因为路径会在calc(x,0)和calc(y,0)中都计算一次
		mn = 1e9, rt = 0, sum = sz[y];
		ask_rt(y, 0), part(rt); // 注意
	}
	return;
}
posted @ 2021-02-01 17:33  andysj  阅读(60)  评论(2编辑  收藏  举报