【树上问题】【模板】点分治

点分治,重心分治。

使用场景:树上大量路径统计问题。

算法原理:

  1. 按照重心划分树,可以使划分的时间复杂度 O(logn)

  2. 分治的思想在于,讨论每一棵子树时,经过或者不经过根节点。

P3806 【模板】点分治1#

题意#

给定一个带权无向图,问树上有多少点对之间距离 =k

思路#

可以分为两种情况:

image

那我们就可以这样统计它们:

solve(k):1. 统计所有经过根节点且满足条件的路径;2. 如果存在子节点 to,那么调用 solve(to)

这样所有合法路径都被统计到了。

我们每次都取树的重心作为根节点可以使算法时间复杂度比较优秀,这样算法时间复杂度为 O(nmlogn)m 为询问次数。

接下来我们说说怎么统计所有经过该点且满足条件的路径数量。

dis[i] 表示每个点距离其子树的根的距离。

  1. 清空 dis 数组。
  2. 计算出每个点距离其子树的根的距离 dis[i]
  3. 如果询问为 ask,那么如果在之前计算的 dis 中有 askdis[i],那么说明原来的那条路径和这次计算出来的 dis[i] 可以组成一条长度为 ask 的路径。
    3. 1. 因为清空过 dis 数组,所以不太好处理,我们可以使用 bool 数组arr统计树上每个点到根节点的距离(就相当于开一个桶,如果树中某一个点与根节点的距离为 dis,那么可以将arr[dis] +1)。

代码#

注意:子树的重心求出来后,记得更新其父结点的子树大小 sz,因为现在父结点已经成为重心的子结点,所以写上 sz[fa] = sum - sz[u]

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010, M = 110, INF = 10000010;

struct Edge {
	int to;
	int next;
	int w;
}e[N * 2];

int head[N], idx;

void add(int a, int b, int c) {
	idx++;
	e[idx].to = b;
	e[idx].next = head[a];
	e[idx].w = c;
	head[a] = idx;
}

int n, m;
int root;                   // 求每个树的重心,从重心开始便利
int sz[N];                  // 树(子树)大小
int ask[M], ans[M];         // 离线处理
bool del[N];                // 是否处理完(是否删除)
int sum;                    // 求重心专用

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_root(to, u);
		if (root != -1) return;
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= sum / 2) {
		root = u;
		sz[fa] = sum - sz[u];
	}
}

int dis[N], cnt, d[N];      // 保存一个点距离根节点的距离

void get_dis(int u, int fa) {
	dis[++cnt] = d[u];
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		d[to] = d[u] + e[i].w;
		get_dis(to, u);
	} 
}

int q[N], top;            // 记录要清空的位置,如果全部memset会T
bool value[INF];            // 开桶记录

void calc(int u) {
	value[0] = 1;           // 处理一个端点在u的情况
	top = 0;                //清空队列
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;
		
		cnt = 0;
		d[to] = e[i].w;
		get_dis(to, u);
		
		for (int j = 1; j <= cnt; j++) {
			for (int k = 1; k <= m; k++) {
				if (ask[k] >= dis[j]) {
					ans[k] |= value[ask[k] - dis[j]];// 处理结果
				}
			}
		}
		
		for (int j = 1; j <= cnt; j++) {    // 将新的距离放入桶
		    if (dis[j] <= INF) {
    			value[dis[j]] = 1;
    			q[++top] = dis[j];
		    }
		}
	}
	for (int i = 1; i <= top; i++) value[q[i]] = 0;// 清空
}

void divide(int u) {
	calc(u);
	del[u] = 1;                             // 删除
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;
		sum = sz[to];
		root = -1;
		get_root(to, u);                    // 从重心往下走
		divide(root);
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n >> m;
	for (int i = 1; i < n; i++) {
		int x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
		add(y, x, z); 
	}
	
	for (int i = 1; i <= m; i++) cin >> ask[i];
	
	sum = n;
	root = -1;
	get_root(1, 0);             // 求正棵树的重心
	divide(root);
	
	for (int i = 1; i <= m; i++) {  // 输出
		if (ans[i]) cout << "AYE" << '\n';
		else cout << "NAY" << '\n';
	}
	return 0;
}

P4178 Tree#

要维护 可以使用前缀和思想,可以使用树状数组来维护。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 40010, INF = 40010;

int value[INF];

void add(int u, int x) {
    u++;
    for (; u < INF; u += u & -u) value[u] += x;
}

int query(int u) {
    u++;
    int res = 0;
    for (; u; u -= u & -u) res += value[u];
    return res;
}

struct Edge {
    int to;
    int next;
    int w;
}e[N * 2];

int head[N], idx;

void add(int a, int b, int c) {
    idx++;
    e[idx].to = b;
    e[idx].next = head[a];
    e[idx].w = c;
    head[a] = idx;
}

int n, ask, ans;

int sum, root, sz[N], del[N];

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_root(to, u);
		if (root != -1) return;
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= sum / 2) {
		root = u;
		sz[fa] = sum - sz[u];
	}
}

int d[N], dis[N], cnt;

void get_dis(int u, int fa) {
    dis[++cnt] = d[u];
    for (int i = head[u]; i; i = e[i].next) {
        int to = e[i].to;
        if (to == fa || del[to]) continue;
        d[to] = d[u] + e[i].w;
        get_dis(to, u);
    }
}

queue<int> q;

void calc(int u) {
    for (int i = head[u]; i; i = e[i].next) {
        int to = e[i].to;
        if (del[to]) continue;

        cnt = 0;
        d[to] = e[i].w;
        get_dis(to, u);

        for (int j = 1; j <= cnt; j++) {
            if (ask >= dis[j]) {
                ans += query(ask - dis[j]);
            }
        }

        for (int j = 1; j <= cnt; j++) {
            if (dis[j] < INF) {
                add(dis[j], 1);
                q.push(dis[j]);
            }
        }
    }
    while (q.size()) {
        add(q.front(), -1);
        q.pop();
    }
}

void divide(int u) {
    calc(u);
    del[u] = 1;

    for (int i = head[u]; i; i = e[i].next) {
        int to = e[i].to;
        if (del[to]) continue;
        sum = sz[to];
        root = -1;
        get_root(to, u);
        divide(root);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i < n; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    cin >> ask;

    add(0, 1);
    
    sum = n;
    root = -1;
    get_root(1, 0);
    divide(root);
    
    cout << ans << '\n';
    return 0;
}

P2634 [国家集训队]聪聪可可#

维护的信息变为距离 dismod3 即可。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 20010, INF = 0x3f3f3f3f;

struct Edge {
	int to;
	int next;
	int w;
}e[N * 2];

int head[N], idx;

void add(int a, int b, int c) {
	idx++;
	e[idx].to = b;
	e[idx].next = head[a];
	e[idx].w = c;
	head[a] = idx;
}

int n;

int sz[N], del[N];
int minsize, sum, root;

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_root(to, u);
		if (root != -1) return;
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= sum / 2) {
		root = u;
		sz[fa] = sum - sz[u];
	}
}

int dis[N], d[N], cnt;
int ans;
int value[5];

void get_dis(int u, int fa) {
	dis[++cnt] = d[u];
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		d[to] = d[u] + e[i].w;
		get_dis(to, u);
	}
}

void calc(int u) {
	value[0] = 1;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;

		cnt = 0;
		d[to] = e[i].w;
		get_dis(to, u);

		for (int j = 1; j <= cnt; j++) ans += value[(3 - dis[j] % 3) % 3];
		for (int j = 1; j <= cnt; j++) value[dis[j] % 3]++;
	}
	memset(value, 0, sizeof(value));
}

void divide(int u) {
	calc(u);
	del[u] = 1;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;
		sum = sz[to];
		root = -1;
		get_root(to, u);
		divide(root);
	}
}

int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> n;
	for (int i = 1; i < n; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
		add(b, a, c);
	}

	sum = n;
	root = -1;
	get_root(1, 0);
	divide(root);

	ans = ans * 2 + n;
	int g = gcd(ans, n * n);
	cout << ans / g << '/' << n * n / g << '\n';
	return 0;
}

P4149 [IOI2011]Race#

需要维护两个信息。

  1. 距离 d1
  2. 路径长度 d2
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 200010, M = 1000010;

struct Edge {
	int to;
	int next;
	int w;
}e[N * 2];

int head[N], idx;

void add(int a, int b, int c) {
	idx++;
	e[idx].to = b;
	e[idx].next = head[a];
	e[idx].w = c;
	head[a] = idx;
}

int n, ask;
int root, sz[N];
bool del[N];
int sum;

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_root(to, u);
		if (root != -1) return;
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= (sum >> 1)) {
		root = u;
		sz[fa] = sum - sz[u];
	}
}

int ans = 0x3f3f3f3f;
int minpath[M];

int pathcnt[N];
int dis[N];
int cnt = 0;

void get_dis(int u, int fa, int d1, int d2) {
	if (d1 > ask) return;
	cnt++;
	dis[cnt] = d1;
	pathcnt[cnt] = d2;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_dis(to, u, d1 + e[i].w, d2 + 1);
	}
}

int q[N], top;

void calc(int u) {
	minpath[0] = 0;
	top = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;

		cnt = 0;
		get_dis(to, u, e[i].w, 1);

		for (int j = 1; j <= cnt; j++) {
			// minpath[ask - dis[j]] value[ask - dis[j]]
			if (ask < dis[j]) continue;
			ans = min(ans, pathcnt[j] + minpath[ask - dis[j]]);
		}

		for (int j = 1; j <= cnt; j++) {
			minpath[dis[j]] = min(minpath[dis[j]], pathcnt[j]);
			q[++top] = dis[j];
		}
	}
	for (int i = 1; i <= top; i++) minpath[q[i]] = 0x3f3f3f3f;
}

void divide(int u) {
	del[u] = 1;
	calc(u);
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;
		sum = sz[to];
		root = -1;
		get_root(to, u);
		divide(root);
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> n >> ask;
	for (int i = 1; i < n; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		a++, b++;
		add(a, b, c);
		add(b, a, c);
	}

	memset(minpath, 0x3f, sizeof(minpath));
	sum = n;
	root = -1;
	get_root(1, 0);
	divide(root);
	if (ans >= n) cout << -1 << '\n';
	else cout << ans << '\n';
	return 0;
}

CF321C Ciel the Commander#

按照重心往下赋值即可,如果深度超过 26 即为无解,但实际上如果一直沿重心往下走,总共只有 logn 层,所以原题不存在无解情况。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

struct Edge {
	int to;
	int next;
}e[N * 2];

int head[N], idx;

void add(int a, int b) {
	idx++;
	e[idx].to = b;
	e[idx].next = head[a];
	head[a] = idx;
}

int n;
bool del[N];
int sz[N];
int sum, root;

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa || del[to]) continue;
		get_root(to, u);
		if (root != -1) return;
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= sum / 2) {
		root = u;
		sz[fa] = sum - sz[u];
	}
}

char res[N];

void divide(int u, char ch) {
	del[u] = 1;
	res[u] = ch;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (del[to]) continue;
		sum = sz[to];
		root = -1;
		get_root(to, u);
		divide(root, ch + 1);
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> n;
	for (int i = 1; i < n; i++) {
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}

	sum = n;
	root = -1;
	get_root(1, 0);

	divide(root, 'A');

	for (int i = 1; i <= n; i++) cout << res[i] << ' ';
	cout << '\n';
	return 0;
}
posted @   SunnyYuan  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示