Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

点分治

点分治是一种树形分治的算法,它选择当前连通块一个重心,将树分成多个连通块进行处理,然后对每个连通块进行分治递归。

树的重心

树上的一个点,满足它的儿子的 \(size\) 的最大值最小。

可以证明,一个树有一个或者两个重心。若有两个重心,它俩一定是直接相连的。

处理问题

  1. 求树上给定路径联通的点的对数。我们发现将树分治后,形成了若干个连通块,而且这些连通块必定经过重心。那么我们就可以 \(O(\log n)\)次地计算每一块之间的联通路径数。那么怎么计算呢?
    • 两两之间分别计算
    • 利用容斥原理,在每一个分治中计算\(sum_i^n\)
    • 编号计算
  2. 求树上给定路径到给定点的路径权和。对于每一个连通块,因为有连通块内部和到上一层重心两个距离,我们必须利用动态开点线段树 时间复杂度\(O(n \log^2 n)\)

点分树和动态点分树

点分树就是将每一层的重心与下一层的子树的所有重心连边所构成的树。 只能用来处理路径相关问题。

知识交叉——替罪羊树

替罪羊树是一个二叉平衡搜索树,它有一个阈值,一般为 \(0.75\),当一个点的一个子树大小大于该树的大小乘以阈值,那么我们就把它拍扁暴力重构成新的树。

那么点分树有一个类似的阈值,当一个子连通块的大小大于上层树大小乘阈值,我们分治重构它。

当然,之所以会不平衡,是因为插入了新的点,进行了修改。

例题

  • 捉迷藏
  • 紫荆花之恋
  • 震波

边分治

同样是对树进行分治,只不过这次是找一条边将左右两边分成两个连通块。

对于多子树点的处理

因为多子树点(比如菊花图)是无法直接进行边分治的,我们可以插入虚点和虚边构成二叉树解决。即边分二叉树。 虚点和虚边是没有贡献的,所以这样就可以保证答案正确。 插入虚点和虚边的方法是:给每个子节点连上虚点,然后给虚点之间连边,给第一个虚点向节点连边,于是就成了二叉树。

边分树

我们发现,每一层都分成了两个连通块,我们构建出一个点连接两个连通块,pushup 上子树的权值。

具体地,先对 \(1\) 树进行边分治,每个点初始的边分树是一条链,考虑对每个点构造出这个边分树。

开始只有根。根其实就是记录分治时候是在那个位置。定义连接分治重心 root 深度较小的连通块为右部点,另一个为左部点,保存每个点作为左部点还是右部点。然后在每个之前最后一个加入位置 lasti 下面加入左儿子或者右儿子。

lasti 位置保留这个信息 vlvr。初始是 -inf

左部点贡献权值:dis[x]

右部点贡献:dis[x]-dis[lca]

lca 一定在右部点。

其实这就是合并二叉树:\(O(nlogn)\) 次地合并,每次在分支处计算贡献,并只加上不重合的点。时间复杂度是和线段树合并类似的。

例题

《暴力写挂》

(以下代码来自某划水选手)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <bits/stdc++.h>
using namespace std;

namespace TYC
{
typedef long long ll;
const int N = 366666 * 2 + 5, M = 20;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;

int n, node_cnt, rot[N];
ll ans;
struct edge { int v, w; };
struct node { int son[2]; ll val; } tr[N * M];

namespace T1
{
int tot, size[N], Head[N], now[N];
ll dis[N];
vector<edge> T[N];
struct Edge { int to, next, w, vis; } E[N << 1];

inline void add(const int u, const int v, const int w)
{
static int ec = 1;
E[++ec] = {v, Head[u], w, 0};
Head[u] = ec;
E[++ec] = {u, Head[v], w, 0};
Head[v] = ec;
}

void buildtree(const int u, const int f)
{
for (auto p : T[u])
if (p.v != f)
{
dis[p.v] = dis[u] + p.w;
buildtree(p.v, u);
}
static vector<edge> son;
son.clear();
for (auto p : T[u])
if (p.v != f)
son.push_back(p);
int sz = int(son.size());
if (!sz)
return;
for (int i = 0; i < sz; i++)
add(tot + i + 1, son[i].v, son[i].w);
for (int i = 1; i < sz; i++)
add(tot + i, tot + i + 1, 0);
add(u, tot + 1, 0);
tot += sz;
}

void build()
{
tot = n;
for (int i = 1, u, v, w; i < n; i++)
{
scanf("%d%d%d", &u, &v, &w);
T[u].push_back({v, w});
T[v].push_back({u, w});
}
buildtree(1, 0);
for (int i = 1; i <= n; i++)
rot[i] = now[i] = ++node_cnt;
}

void getsize(const int u, const int f)
{
size[u] = 1;
for (int i = Head[u], v; i; i = E[i].next)
if (!E[i].vis && (v = E[i].to) != f)
{
getsize(v, u);
size[u] += size[v];
}
}

void getedge(const int u, const int f, const int sz, int &p)
{
for (int i = Head[u], v; i; i = E[i].next)
if (!E[i].vis && (v = E[i].to) != f)
{
if (max(size[v], sz - size[v]) < max(size[E[p].to], sz - size[E[p].to]))
p = i;
getedge(v, u, sz, p);
}
}

void dfs(const int u, const int f, const ll d, const int c)
{
if (u <= n)
{
tr[now[u]].son[c] = ++node_cnt;
tr[now[u] = node_cnt].val = d + dis[u];
}
for (int i = Head[u], v; i; i = E[i].next)
if (!E[i].vis && (v = E[i].to) != f)
dfs(v, u, d + E[i].w, c);
}

void solve(const int x)
{
getsize(x, 0);
if (size[x] == 1)
return;
int p = 0, u, v;
getedge(x, 0, size[x], p);
E[p].vis = E[p ^ 1].vis = 1;
dfs(u = E[p].to, 0, E[p].w, 0);
dfs(v = E[p ^ 1].to, 0, 0, 1);
solve(u);
solve(v);
}
}

namespace T2
{
ll res;
vector<edge> E[N];

void update(const int x, const int y)
{
if (x && y)
ans = max(ans, tr[x].val + tr[y].val - res);
}

void merge(int &x, const int y)
{
if (!x || !y)
return void(x = x + y);
tr[x].val = max(tr[x].val, tr[y].val);
update(tr[x].son[0], tr[y].son[1]);
update(tr[x].son[1], tr[y].son[0]);
merge(tr[x].son[0], tr[y].son[0]);
merge(tr[x].son[1], tr[y].son[1]);
}

void dfs(const int u, const int f, const ll d)
{
ans = max(ans, (T1::dis[u] - d) * 2);
for (auto p : E[u])
if (p.v != f)
{
dfs(p.v, u, d + p.w);
res = d * 2;
merge(rot[u], rot[p.v]);
}
}

void solve()
{
for (int i = 1, u, v, w; i < n; i++)
{
scanf("%d%d%d", &u, &v, &w);
E[u].push_back({v, w});
E[v].push_back({u, w});
}
dfs(1, 0, 0);
}
}

void work()
{
scanf("%d", &n);
T1::build();
T1::solve(1);
T2::solve();
printf("%lld\n", ans >> 1);
}
}

int main()
{
TYC::work();
return 0;
}

给小狼留言