【树上问题】【模板】点分治
点分治,重心分治。
使用场景:树上大量路径统计问题。
算法原理:
-
按照重心划分树,可以使划分的时间复杂度
。 -
分治的思想在于,讨论每一棵子树时,经过或者不经过根节点。
P3806 【模板】点分治1#
题意#
给定一个带权无向图,问树上有多少点对之间距离
思路#
可以分为两种情况:
那我们就可以这样统计它们:
这样所有合法路径都被统计到了。
我们每次都取树的重心作为根节点可以使算法时间复杂度比较优秀,这样算法时间复杂度为
接下来我们说说怎么统计所有经过该点且满足条件的路径数量。
设
- 清空
数组。 - 计算出每个点距离其子树的根的距离
。 - 如果询问为
,那么如果在之前计算的 中有 ,那么说明原来的那条路径和这次计算出来的 可以组成一条长度为 的路径。
3. 1. 因为清空过 数组,所以不太好处理,我们可以使用bool
数组arr
统计树上每个点到根节点的距离(就相当于开一个桶,如果树中某一个点与根节点的距离为 ,那么可以将arr[dis]
)。
代码#
注意:子树的重心求出来后,记得更新其父结点的子树大小 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 [国家集训队]聪聪可可#
维护的信息变为距离
#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#
需要维护两个信息。
- 距离
。 - 路径长度
。
#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#
按照重心往下赋值即可,如果深度超过
#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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析