• 「点分治」学习笔记


    「点分治」学习笔记

    什么是点分治

    拆开来说,“点”就是树上的每一个点,“分治”就是类似 (CDQ) 的分治,用来解决和处理带权无根树(无向树,即每个点都可以当根)上路径统计问题。

    基本思路

    对于一个树上的路径,无非分成两种情况:

    • 经过根节点的一条路径
    • 在一个子树里的路径

    这样我们就可以利用分治的思想,先处理一颗大树,只统计经过根节点的路径,再处理它的每个子树。

    STEP1

    • 寻找树的重心

    (树的重心:其最大子树大小比其他任何一个节点当根的最大子树大小都小)

    为什么要找树的重心呢,用一个例子来说明:

    如果树是一条长度为 (5) 的链状,我们用最上面的 (1) 结点来处理,复杂度就是 (5)

    但是,我们如果用其重心 ((3)) 来处理,复杂度就是 (2) 了。

    所以求树的重心保证了我们的时间复杂度。

    void FindRoot (int u, int fa) { // 寻找树的重心
    	size[u] = 1, maxson[u] = 0; // 子树大小和,最大子树大小
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (v == fa || vis[v]) continue; // vis数组很重要,我们后面再说
    		FindRoot (v, u);
    		size[u] += size[v]; // 统计子树大小
    		maxson[u] = max (maxson[u], size[v]); // 求出最大
    	}
    	maxson[u] = max (maxson[u], Tsize - size[u]); // 别忘了它上面的子树(它的爸爸那边的)
    	if (maxson[u] < maxson[root]) root = u; // 更新根节点
    }
    

    STEP2

    • 计算并统计其子树节点到它的路径

    这里就要用到我们的分治思想了。

    首先我们先 (DFS) 一遍统计一下每个根节点到下面节点的路径长度。

    void DFS (int u, int fa, int d) { // d为当前节点u到root的距离
    	w[++ cnt] = d; // 记录到数组里面,一会儿分治用
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (v == fa || vis[v]) continue;
    		DFS (v, u, d + e[i].w); // 计算距离
    	}
    }
    
    • 分治

    我们将上面记录的距离数组,从小到大排序一边。

    定义一个左指针 (l) ,一个右指针 (r), 如果说 (w[l] + w[r] > k) 将右指针左移, 如果 (w[l] + w[r] <= k) 那么 (r) 左边的所有路径都可以满足条件,将答案加上 (r - l + 1) 即可。


    我们现在将每个根节点到自己的为 (0) 路径也算到答案里面了,所以我们最后需要减去一个 (n)

    同时我们也将同一个子树里面结点的路径也加上了,所以我们在总的点分治里面,再减去其子树所特殊统计到的答案。


    inline int Calc (int u, int d) {
    	register int sum = 0;
    	cnt = 0;
    	DFS (u, 0, d); // 统计路径
    	sort (w + 1, w + cnt + 1); // 从小到大排序
    	register int r = cnt;
    	for (register int l = 1; l <= cnt; l ++) {
    		while (w[l] + w[r] > k && r >= 1) r --; // 左移右指针
    		if (l > r) break;
    		sum += r - l + 1;
    	}
    	return sum;
    }
    

    STEP3

    • 最后就是点分治的核心了

    先加上到当前根节点的答案,便历其子节点,将其子节点基础距离为 (e[i].w)的答案减去。

    别忘了在处理每颗子树的时候,也要更新一边重心。

    void CDQ (int u) {
    	ans += Calc (u, 0);
    	vis[u] = 1;
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (vis[v]) continue;
    		ans -= Calc (v, e[i].w); // 减去冗余的答案
    		root = 0;
    		Tsize = size[v]; // 更新当前树的大小和根节点
    		FindRoot (v, u);
    		CDQ (root); // 处理每个子树
    	}	
    }
    

    时间复杂度

    好像差不多是 (O(nlog^2_n))证明略

    模板题

    某谷P4178

    代码

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    #define DEBUG puts ("emmmm")
    
    using namespace std;
    
    const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
    
    inline int read () {
    	register int x = 0, w = 1;
    	char ch = getchar();
    	for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
    	for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
    	return x * w;
    }
    
    int n, k, root, Tsize, ans, cnt;
    int size[maxn], maxson[maxn], w[maxn];
    bool vis[maxn];
    
    struct Edge {
    	int to, next, w;
    }e[maxn << 1];
    
    int tot, head[maxn];
    
    void Add (int u, int v, int w) {
    	e[++tot].to = v;
    	e[tot].w = w;
    	e[tot].next = head[u];
    	head[u] = tot;
    }
    
    void FindRoot (int u, int fa) { // 寻找树的重心
    	size[u] = 1, maxson[u] = 0; // 子树大小和,最大子树大小
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (v == fa || vis[v]) continue; // vis数组很重要,我们后面再说
    		FindRoot (v, u);
    		size[u] += size[v]; // 统计子树大小
    		maxson[u] = max (maxson[u], size[v]); // 求出最大
    	}
    	maxson[u] = max (maxson[u], Tsize - size[u]); // 别忘了它上面的子树(它的爸爸那边的)
    	if (maxson[u] < maxson[root]) root = u; // 更新根节点
    }
    
    void DFS (int u, int fa, int d) { // d为当前节点u到root的距离
    	w[++ cnt] = d; // 记录到数组里面,一会儿分治用
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (v == fa || vis[v]) continue;
    		DFS (v, u, d + e[i].w); // 计算距离
    	}
    }
    
    inline int Calc (int u, int d) {
    	register int sum = 0;
    	cnt = 0;
    	DFS (u, 0, d); // 统计路径
    	sort (w + 1, w + cnt + 1); // 从小到大排序
    	register int r = cnt;
    	for (register int l = 1; l <= cnt; l ++) {
    		while (w[l] + w[r] > k && r >= 1) r --; // 左移右指针
    		if (l > r) break;
    		sum += r - l + 1;
    	}
    	return sum;
    }
    
    void CDQ (int u) {
    	ans += Calc (u, 0);
    	vis[u] = 1;
    	for (register int i = head[u]; i; i = e[i].next) {
    		register int v = e[i].to;
    		if (vis[v]) continue;
    		ans -= Calc (v, e[i].w); // 减去冗余的答案
    		root = 0;
    		Tsize = size[v]; // 更新当前树的大小和根节点
    		FindRoot (v, u);
    		CDQ (root); // 处理每个子树
    	}	
    }
    
    int main () {
    	//freopen ("tree.in", "r", stdin);
    	//freopen ("tree.out", "w", stdout);
    	n = read();
    	for (register int i = 1; i <= n - 1; i ++) {
    		register int u = read(), v = read(), w = read();
    		Add (u, v, w), Add (v, u, w);
    	}
    	k = read();
    	maxson[root = 0] = INF, Tsize = n;
    	FindRoot (1, 0);
    	CDQ (root);
    	printf ("%d
    ", ans - n);
    	return 0;
    }
    
  • 相关阅读:
    HTML-标题
    HTML-属性
    HTML-元素
    前端 vue Request Payload VS Form Data
    JWT,Session、Cookie(httpOnly,secure) 登录认证
    js 定位当前城市(ip,省份,城市,邮编)接口定位(搜狐、新浪、百度、腾讯API)
    sqlsugar 使用汇总
    asp.net 跨域
    asp.net core程序发布(Webapi,web网站)
    .net 多线程发展1.0--4.0
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/13713368.html
Copyright © 2020-2023  润新知