【学习笔记】点分治 & 点分树
Mier_Samuelle · · 算法·理论
点分治
点分治是一种树上分治算法,常用于解决各类树上路径问题。
模板题
:::info[例题 0]{open}
P3806 【模板】点分治
给定一棵有
0.2s,
:::
设置分治中心
受此启发,我们枚举
每层递归需要遍历当前连通块中的所有节点。当递归层数为
实现上需要注意,每层递归结束后需要清空
:::success[Code]{open}
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e4 + 10, MAXM = 110, MAXK = 1e7 + 10;
vector <pair<int, int>> adj[MAXN];
int qr[MAXM], siz[MAXN], que[MAXN], tmp[MAXN], n, m, cnt, cur;
bool vis[MAXN], flag[MAXK], ans[MAXM];
void dfs1(int u, int p){
siz[u] = 1;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int u, int p, int d){
tmp[++cnt] = d;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(v, u, d + w);
}
return;
}
void solve(int u){
cnt = cur = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = flag[0] = true;
que[++cur] = 0;
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
cnt = 0;
dfs3(v, rt, w);
for (int i = 1; i <= cnt; i++){
for (int j = 1; j <= m; j++){
if (qr[j] - tmp[i] >= 0){
ans[j] |= flag[qr[j] - tmp[i]];
}
}
}
for (int i = 1; i <= cnt; i++){
if (tmp[i] < MAXK){
que[++cur] = tmp[i];
flag[tmp[i]] = true;
}
}
}
for (int i = 1; i <= cur; i++){
flag[que[i]] = false;
}
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
solve(v);
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i < n; i++){
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
for (int i = 1; i <= m; i++){
cin >> qr[i];
}
solve(1);
for (int i = 1; i <= m; i++){
cout << (ans[i] ? "AYE\n" : "NAY\n");
}
return 0;
}
:::
例题
I
:::info[例题 1]{open}
P4178 Tree
给定一棵有
1s,
:::
大体思路完全一致。区别只在于,本题需要计数,需将
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 4e4 + 10;
vector <pair<int, int>> adj[MAXN];
int siz[MAXN], que[MAXN], dis[MAXN], n, k, cur, cnt, ans;
bool vis[MAXN];
struct BIT{
int v[MAXN];
int lowbit(int x){
return x & (-x);
}
void modify(int u, int x){
while (u <= 4e4 + 1){
v[u] += x;
u += lowbit(u);
}
return;
}
int query(int u){
int res = 0;
while (u){
res += v[u];
u -= lowbit(u);
}
return res;
}
}tr;
void dfs1(int u, int p){
siz[u] = 1;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int u, int p, int d){
dis[++cnt] = d;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(v, u, d + w);
}
return;
}
void solve(int u){
cur = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
tr.modify(1, 1);
que[++cur] = 1;
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
cnt = 0;
dfs3(v, rt, w);
for (int i = 1; i <= cnt; i++){
if (k - dis[i] >= 0){
ans += tr.query(k - dis[i] + 1);
}
}
for (int i = 1; i <= cnt; i++){
if (dis[i] <= 4e4){
tr.modify(dis[i] + 1, 1);
que[++cur] = dis[i] + 1;
}
}
}
for (int i = 1; i <= cur; i++){
tr.modify(que[i], -1);
}
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
solve(v);
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i < n; i++){
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
cin >> k;
solve(1);
cout << ans << "\n";
return 0;
}
:::
II
:::info[例题 2]{open}
P4149 [IOI 2011] Race
给定一棵有
3s,
:::
依旧是一样的思路。区别只在于,本题要求最小边数,需将
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10, MAXM = 1e6 + 10;
const int INF = 0x3f3f3f3f;
vector <pair<int, int>> adj[MAXN];
int siz[MAXN], minn[MAXM], que[MAXN], dis[MAXN], dep[MAXN], n, k, ans, cur, cnt;
bool vis[MAXN];
void dfs1(int u, int p){
siz[u] = 1;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int u, int p, int d1, int d2){
dis[++cnt] = d1;
dep[cnt] = d2;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(v, u, d1 + w, d2 + 1);
}
return;
}
void solve(int u){
cur = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
minn[0] = 0;
que[++cur] = 0;
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
cnt = 0;
dfs3(v, rt, w, 1);
for (int i = 1; i <= cnt; i++){
if (k - dis[i] >= 0){
ans = min(ans, minn[k - dis[i]] + dep[i]);
}
}
for (int i = 1; i <= cnt; i++){
if (dis[i] <= 1e6){
minn[dis[i]] = min(minn[dis[i]], dep[i]);
que[++cur] = dis[i];
}
}
}
for (int i = 1; i <= cur; i++){
minn[que[i]] = INF;
}
for (auto [v, w] : adj[rt]){
if (!vis[v]){
solve(v);
}
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i < n; i++){
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
memset(minn, 0x3f, sizeof(minn));
ans = INF;
solve(1);
cout << (ans > 1e9 ? -1 : ans) << "\n";
return 0;
}
:::
III
:::info[例题 3]{open}
P6626 [省选联考 2020 B 卷] 消息传递
给定一棵有
2s,
:::
仿照模板题的做法,将询问离线下来挂在
据此,我们先遍历
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
vector <int> adj[MAXN];
vector <pair<int, int>> qr[MAXN], idx[MAXN];
int siz[MAXN], qx[MAXN], qk[MAXN], cnt[MAXN], que[MAXN], ans[MAXN], n, m, cur;
bool vis[MAXN];
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int rt, int u, int p, int d){
que[++cur] = d;
idx[rt].push_back({u, d});
cnt[d]++;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(rt, v, u, d + 1);
}
return;
}
void dfs4(int u, int p, int d, int o){
cnt[d] += o;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs4(v, u, d + 1, o);
}
return;
}
void solve(int u){
cur = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
cnt[0]++;
que[++cur] = 0;
for (int v : adj[rt]){
if (vis[v]){
continue;
}
dfs3(v, v, rt, 1);
}
for (auto [k, id] : qr[rt]){
ans[id] += cnt[k];
}
for (int v : adj[rt]){
if (vis[v]){
continue;
}
dfs4(v, rt, 1, -1);
for (auto [x, d] : idx[v]){
for (auto [k, id] : qr[x]){
if (k >= d){
ans[id] += cnt[k - d];
}
}
}
dfs4(v, rt, 1, 1);
idx[v].clear();
}
for (int i = 1; i <= cur; i++){
cnt[que[i]] = 0;
}
for (int v : adj[rt]){
if (vis[v]){
continue;
}
solve(v);
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--){
cin >> n >> m;
for (int i = 1; i <= n; i++){
adj[i].clear();
qr[i].clear();
vis[i] = false;
}
for (int i = 1; i <= m; i++){
ans[i] = 0;
}
for (int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
for (int i = 1; i <= m; i++){
cin >> qx[i] >> qk[i];
qr[qx[i]].push_back({qk[i], i});
}
solve(1);
for (int i = 1; i <= m; i++){
cout << ans[i] << "\n";
}
}
return 0;
}
:::
IV
:::info[例题 4]{open}
P10421 [蓝桥杯 2023 国 A] 树上的路径
给定一棵有
6s,
:::
一个自然的想法是,对于一条长度为
考虑将这个偏移量分离出来:
那么对于左边这部分,就可以用上面的方法计算了,右边的部分系数固定,也是容易计算的。具体地,开两棵树状数组,一棵在加入时乘上系数(维护左半部分),另一棵不乘系数(维护右半部分),查询时求区间和即可。
:::success[Code]
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 10;
vector <int> adj[MAXN];
int siz[MAXN], que[MAXN], dis[MAXN], n, L, R, cur, cnt, ans;
bool vis[MAXN];
struct BIT{
int v[MAXN];
int lowbit(int x){
return x & (-x);
}
void modify(int u, int x){
while (u < MAXN){
v[u] += x;
u += lowbit(u);
}
return;
}
int query(int u){
int res = 0;
while (u){
res += v[u];
u -= lowbit(u);
}
return res;
}
}tr1, tr2;
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int u, int p, int d){
dis[++cnt] = d;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(v, u, d + 1);
}
return;
}
void solve(int u){
cur = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
tr2.modify(1, 1);
vis[rt] = true;
que[++cur] = 1;
for (int v : adj[rt]){
if (vis[v]){
continue;
}
cnt = 0;
dfs3(v, rt, 1);
for (int i = 1; i <= cnt; i++){
if (dis[i] > R){
continue;
}
else if (dis[i] > L){
ans += tr1.query(R - dis[i] + 1) + dis[i] * tr2.query(R - dis[i] + 1);
}
else{
ans += tr1.query(R - dis[i] + 1) - tr1.query(L - dis[i]) + dis[i] * (tr2.query(R - dis[i] + 1) - tr2.query(L - dis[i]));
}
}
for (int i = 1; i <= cnt; i++){
que[++cur] = dis[i] + 1;
tr1.modify(dis[i] + 1, dis[i]);
tr2.modify(dis[i] + 1, 1);
}
}
for (int i = 1; i <= cur; i++){
tr1.modify(que[i], 1 - que[i]);
tr2.modify(que[i], -1);
}
for (int v : adj[rt]){
if (vis[v]){
continue;
}
solve(v);
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> L >> R;
for (int i = 2; i <= n; i++){
int p;
cin >> p;
adj[i].push_back(p);
adj[p].push_back(i);
}
solve(1);
cout << ans << "\n";
return 0;
}
:::
V
:::info[例题 5]{open}
P3714 [BJOI2017] 树的难题
给定一棵有
2s,
:::
对于分治中心
考虑分别维护这两种情况,开两棵线段树,均以路径长度为下标,一棵维护与当前子树起始边颜色不同的路径的最大权值,另一棵维护与当前子树起始边颜色相同的路径的最大权值。为了让颜色相同的边一起被处理,在跑点分治之前,我们将每个节点的出边按颜色排序。这样我们每处理完一棵子树,就先把该子树的信息插入第二棵线段树,然后在处理下一棵子树前,如果起始边的颜色发生了改变,再把第二棵线段树中的信息暴力合并到第一棵线段树中。
:::success[Code]
#include <bits/stdc++.h>
#define lc (u << 1)
#define rc ((u << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
const int MAXN = 2e5 + 10;
const int INF = 0x3f3f3f3f;
vector <pair<int, int>> adj[MAXN];
int c[MAXN], siz[MAXN], que1[MAXN], que2[MAXN], dis[MAXN], sum[MAXN], n, m, L, R, cur1, cur2, cur3, ans = -INF;
bool vis[MAXN];
struct Segment_tree{
int mx[MAXN * 4];
void pushup(int u){
mx[u] = max(mx[lc], mx[rc]);
return;
}
void build(int u, int l, int r){
mx[u] = -INF;
if (l == r){
return;
}
build(lc, l, mid);
build(rc, mid + 1, r);
return;
}
void chkmax(int u, int l, int r, int pos, int val){
if (l == r){
mx[u] = max(mx[u], val);
return;
}
if (pos <= mid){
chkmax(lc, l, mid, pos, val);
}
else{
chkmax(rc, mid + 1, r, pos, val);
}
pushup(u);
return;
}
void assign(int u, int l, int r, int pos, int val){
if (l == r){
mx[u] = val;
return;
}
if (pos <= mid){
assign(lc, l, mid, pos, val);
}
else{
assign(rc, mid + 1, r, pos, val);
}
pushup(u);
return;
}
int query(int u, int l, int r, int ql, int qr){
if (ql <= l && r <= qr){
return mx[u];
}
int res = -INF;
if (ql <= mid){
res = max(res, query(lc, l, mid, ql, qr));
}
if (qr > mid){
res = max(res, query(rc, mid + 1, r, ql, qr));
}
return res;
}
}tr1, tr2;
void dfs1(int u, int p){
siz[u] = 1;
for (auto [col, v] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [col, v] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int u, int p, int d, int x, int last){
dis[++cur3] = d;
sum[cur3] = x;
for (auto [col, v] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(v, u, d + 1, x + (col != last) * c[col], col);
}
return;
}
void solve(int u){
cur1 = cur2 = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
tr2.chkmax(1, 0, n - 1, 0, 0);
que1[++cur1] = 0;
que2[++cur2] = 0;
int last = 0;
for (auto [col, v] : adj[rt]){
if (vis[v]){
continue;
}
if (col != last){
for (int i = 1; i <= cur2; i++){
int tmp = tr2.query(1, 0, n - 1, que2[i], que2[i]);
tr1.chkmax(1, 0, n - 1, que2[i], tmp);
tr2.assign(1, 0, n - 1, que2[i], -INF);
}
cur2 = 0;
}
cur3 = 0;
dfs3(v, rt, 1, c[col], col);
for (int i = 1; i <= cur3; i++){
if (L >= dis[i]){
int tmp1 = tr1.query(1, 0, n - 1, L - dis[i], R - dis[i]) + sum[i];
int tmp2 = tr2.query(1, 0, n - 1, L - dis[i], R - dis[i]) - c[col] + sum[i];
ans = max(ans, max(tmp1, tmp2));
}
else if (R >= dis[i]){
int tmp1 = tr1.query(1, 0, n - 1, 0, R - dis[i]) + sum[i];
int tmp2 = tr2.query(1, 0, n - 1, 0, R - dis[i]) - c[col] + sum[i];
ans = max(ans, max(tmp1, tmp2));
}
}
for (int i = 1; i <= cur3; i++){
tr2.chkmax(1, 0, n - 1, dis[i], sum[i]);
que1[++cur1] = dis[i];
que2[++cur2] = dis[i];
}
last = col;
}
for (int i = 1; i <= cur1; i++){
tr1.assign(1, 0, n - 1, que1[i], -INF);
tr2.assign(1, 0, n - 1, que1[i], -INF);
}
for (auto [col, v] : adj[rt]){
if (!vis[v]){
solve(v);
}
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> L >> R;
for (int i = 1; i <= m; i++){
cin >> c[i];
}
for (int i = 1; i < n; i++){
int u, v, col;
cin >> u >> v >> col;
adj[u].push_back({col, v});
adj[v].push_back({col, u});
}
for (int i = 1; i <= n; i++){
sort(adj[i].begin(), adj[i].end());
}
tr1.build(1, 0, n - 1);
tr2.build(1, 0, n - 1);
solve(1);
cout << ans << "\n";
return 0;
}
:::
VI
:::info[例题 6]{open}
P5306 [COCI 2018/2019 #5] Transport
给定一棵有
1s,
:::
对于分治中心
- 从
u 走到rt 时,显然对于任意一个在u \rightarrow rt 路径上的节点x ,均需满足s_u-s_x \ge t_u-t_x ,即s_x-t_x \le s_u-t_u ; - 走到
rt 后还剩下r=s_u-t_u 的点数。接下来要从rt 走到v ,那么对于任意一个在rt \rightarrow v 路径上的节点y ,均需满足r+s_{fa_y} \ge t_y ,即t_y-s_{fa_y} \le r 。
将
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector <pair<int, ll>> adj[MAXN];
int a[MAXN], n;
int siz[MAXN], cur1, cur2, cur3, cur4;
bool vis[MAXN];
ll b[MAXN], c[MAXN], d[MAXN], e[MAXN], ans;
void dfs1(int u, int p){
siz[u] = 1;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void dfs3(int st, int u, int p, ll mx, ll suma, ll sumd){
mx = max(mx, suma - sumd);
if (mx <= suma - sumd){
b[++cur1] = d[++cur3] = suma - sumd;
}
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs3(st, v, u, mx, suma + a[v], sumd + w);
}
return;
}
void dfs4(int st, int u, int p, ll mx, ll suma, ll sumd){
mx = max(mx, sumd - suma);
c[++cur2] = e[++cur4] = mx;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs4(st, v, u, mx, suma + a[u], sumd + w);
}
return;
}
void build(int u){
cur1 = cur2 = 0;
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
b[++cur1] = a[rt];
c[++cur2] = -INF;
for (auto [v, w] : adj[rt]){
if (vis[v]){
continue;
}
cur3 = cur4 = 0;
dfs3(v, v, rt, a[rt], a[rt] + a[v], w);
dfs4(v, v, rt, -INF, 0, w);
sort(d + 1, d + cur3 + 1, greater<ll>());
sort(e + 1, e + cur4 + 1, greater<ll>());
for (int i = 1, j = 0; i <= cur4; i++){
while (j < cur3 && e[i] <= d[j + 1]){
j++;
}
ans -= j;
}
}
sort(b + 1, b + cur1 + 1, greater<ll>());
sort(c + 1, c + cur2 + 1, greater<ll>());
for (int i = 1, j = 0; i <= cur2; i++){
while (j < cur1 && c[i] <= b[j + 1]){
j++;
}
ans += j;
}
for (auto [v, w] : adj[rt]){
if (!vis[v]){
build(v);
}
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i];
}
for (int i = 1; i < n; i++){
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
build(1);
cout << ans - n << "\n";
return 0;
}
:::
点分树
你可能已经注意到了,上面的所有题目都没有涉及修改操作。若涉及修改,或题目要求强制在线,则需要用到点分树。
模板题
:::info[例题 0]{open}
P6329 【模板】点分树 / 震波
给定一棵有
- 修改某节点的权值;
- 查询与某节点距离不超过
k 的所有节点的权值和。
2s,强制在线,
:::
点分树,顾名思义,是一棵在点分治过程中构造出来的树。具体的构造方法是:将每一层递归的分治中心与上一层的分治中心连边。
点分树有以下两条关键性质:
- 树高为
O(\log n) 级别,这很显然,因为点分治的递归层数就是O(\log n) 级别的; - 任意两节点在点分树上的 lca 一定位于它们在原树的路径上。
其中,第一条性质用于确保复杂度。有了这条性质,许多看似暴力的操作(如在点分树上跳祖先、对每个节点分别开一个数据结构维护子树内信息)都具有了正确的复杂度。
第二条性质用于处理路径信息。回到本题。对于节点
但这样做有个问题:在上面的做法中,我们只考虑了路径经过
对于修改和查询,跳祖先都是
:::success[Code]{open}
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
vector <int> adj[MAXN];
int a[MAXN], fa[MAXN], siz[MAXN], dep[MAXN], ord[MAXN * 2], in[MAXN], mxd1[MAXN], mxd2[MAXN], st[MAXN * 2][20], lg[MAXN * 2], n, m, cur;
bool vis[MAXN];
struct BIT{
vector <int> sum;
void init(int len){
sum.resize(len + 2, 0);
return;
}
int lowbit(int x){
return x & (-x);
}
void modify(int u, int x){
u++;
while (u < (int)sum.size()){
sum[u] += x;
u += lowbit(u);
}
return;
}
int query(int u){
u++;
u = min(u, (int)sum.size() - 1);
int res = 0;
while (u){
res += sum[u];
u -= lowbit(u);
}
return res;
}
}tr1[MAXN], tr2[MAXN];
void dfs_lca(int u, int p){
dep[u] = dep[p] + 1;
ord[++cur] = u;
in[u] = cur;
for (int v : adj[u]){
if (v != p){
dfs_lca(v, u);
ord[++cur] = u;
}
}
}
int get_min(int u, int v){
return (dep[u] < dep[v] ? u : v);
}
void init_st(){
lg[1] = 0;
for (int i = 2; i <= cur; i++){
lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= cur; i++){
st[i][0] = ord[i];
}
for (int i = 1; i <= lg[cur]; i++){
for (int j = 1; j <= cur - (1 << i) + 1; j++){
st[j][i] = get_min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
return;
}
int get_lca(int u, int v){
int l = in[u], r = in[v];
if (l > r){
swap(l, r);
}
int k = lg[r - l + 1];
return get_min(st[l][k], st[r - (1 << k) + 1][k]);
}
int get_dis(int u, int v){
int lca = get_lca(u, v);
return dep[u] + dep[v] - 2 * dep[lca];
}
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void build(int u, int p){
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
fa[rt] = p;
for (int v : adj[rt]){
if (!vis[v]){
build(v, rt);
}
}
return;
}
void modify_tree(int x, int val){
for (int u = x; u; u = fa[u]){
tr1[u].modify(get_dis(u, x), val);
if (fa[u]){
tr2[u].modify(get_dis(fa[u], x), val);
}
}
return;
}
int query_tree(int x, int k){
int sum = tr1[x].query(k);
for (int u = x; fa[u]; u = fa[u]){
int d = get_dis(fa[u], x);
if (k >= d){
sum += tr1[fa[u]].query(k - d);
sum -= tr2[u].query(k - d);
}
}
return sum;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++){
cin >> a[i];
}
for (int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs_lca(1, 0);
init_st();
build(1, 0);
for (int i = 1; i <= n; i++){
for (int u = i; u; u = fa[u]){
mxd1[u] = max(mxd1[u], get_dis(u, i));
if (fa[u]){
mxd2[u] = max(mxd2[u], get_dis(fa[u], i));
}
}
}
for (int i = 1; i <= n; i++){
tr1[i].init(mxd1[i]);
tr2[i].init(mxd2[i]);
}
for (int i = 1; i <= n; i++){
modify_tree(i, a[i]);
}
int ans = 0;
while (m--){
int op;
cin >> op;
if (op == 0){
int x, k;
cin >> x >> k;
x ^= ans;
k ^= ans;
ans = query_tree(x, k);
cout << ans << "\n";
}
else{
int x, y;
cin >> x >> y;
x ^= ans;
y ^= ans;
modify_tree(x, y - a[x]);
a[x] = y;
}
}
return 0;
}
:::
例题
I
:::info[例题 1]{open}
P10603 BZOJ4372 烁烁的游戏
给定一棵有
- 修改与某节点距离不超过
k 的所有节点的权值; - 查询某节点的权值。
3s,
:::
也就是把模板题的修改和查询反了过来。容易发现这并没有本质区别。对于修改,跳
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
vector <int> adj[MAXN];
int a[MAXN], fa[MAXN], siz[MAXN], dep[MAXN], ord[MAXN * 2], in[MAXN], mxd1[MAXN], mxd2[MAXN], st[MAXN * 2][20], lg[MAXN * 2], n, m, cur;
bool vis[MAXN];
struct BIT{
vector <int> sum;
void init(int len){
sum.resize(len + 2, 0);
return;
}
int lowbit(int x){
return x & (-x);
}
void modify(int u, int x){
u++;
while (u < (int)sum.size()){
sum[u] += x;
u += lowbit(u);
}
return;
}
int query(int u){
u++;
u = min(u, (int)sum.size() - 1);
int res = 0;
while (u){
res += sum[u];
u -= lowbit(u);
}
return res;
}
}tr1[MAXN], tr2[MAXN];
void dfs_lca(int u, int p){
dep[u] = dep[p] + 1;
ord[++cur] = u;
in[u] = cur;
for (int v : adj[u]){
if (v != p){
dfs_lca(v, u);
ord[++cur] = u;
}
}
}
int get_min(int u, int v){
return (dep[u] < dep[v] ? u : v);
}
void init_st(){
lg[1] = 0;
for (int i = 2; i <= cur; i++){
lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= cur; i++){
st[i][0] = ord[i];
}
for (int i = 1; i <= lg[cur]; i++){
for (int j = 1; j <= cur - (1 << i) + 1; j++){
st[j][i] = get_min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
return;
}
int get_lca(int u, int v){
int l = in[u], r = in[v];
if (l > r){
swap(l, r);
}
int k = lg[r - l + 1];
return get_min(st[l][k], st[r - (1 << k) + 1][k]);
}
int get_dis(int u, int v){
int lca = get_lca(u, v);
return dep[u] + dep[v] - 2 * dep[lca];
}
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void build(int u, int p){
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
fa[rt] = p;
for (int v : adj[rt]){
if (!vis[v]){
build(v, rt);
}
}
return;
}
void modify_tree(int x, int d, int val){
for (int u = x; u; u = fa[u]){
if (d >= get_dis(u, x)){
tr1[u].modify(0, val);
tr1[u].modify(d - get_dis(u, x) + 1, -val);
}
if (fa[u] && d >= get_dis(fa[u], x)){
tr2[u].modify(0, val);
tr2[u].modify(d - get_dis(fa[u], x) + 1, -val);
}
}
return;
}
int query_tree(int x){
int sum = tr1[x].query(0);
for (int u = x; fa[u]; u = fa[u]){
int d = get_dis(fa[u], x);
sum += tr1[fa[u]].query(d);
sum -= tr2[u].query(d);
}
return sum;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs_lca(1, 0);
init_st();
build(1, 0);
for (int i = 1; i <= n; i++){
for (int u = i; u; u = fa[u]){
mxd1[u] = max(mxd1[u], get_dis(u, i));
if (fa[u]){
mxd2[u] = max(mxd2[u], get_dis(fa[u], i));
}
}
}
for (int i = 1; i <= n; i++){
tr1[i].init(mxd1[i]);
tr2[i].init(mxd2[i]);
}
while (m--){
char op;
cin >> op;
if (op == 'Q'){
int x;
cin >> x;
cout << query_tree(x) << "\n";
}
else{
int x, d, w;
cin >> x >> d >> w;
modify_tree(x, d, w);
}
}
return 0;
}
:::
II
:::info[例题 2]{open}
P2056 [ZJOI2007] 捉迷藏
给定一棵有
- 反转某个点的颜色;
- 求树上一对黑色节点的最大距离。
5s,
:::
称两端点均为黑点的路径为黑点路径。对于任意一条经过了节点
要动态维护最大值和次大值,不难想到 multiset。我们对每个节点
实现上需要注意,不要用 STL multiset,会被卡常,应用手写可删堆代替。
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
vector <int> adj[MAXN];
int fa[MAXN], siz[MAXN], dep[MAXN], ord[MAXN * 2], in[MAXN], st[MAXN * 2][20], lg[MAXN * 2], n, q, cur, cnt;
bool vis[MAXN], flag[MAXN];
struct Heap{
priority_queue <int> q1, q2;
void push(int x){
q1.push(x);
return;
}
void erase(int x){
q2.push(x);
return;
}
void clean(){
while (!q2.empty() && q1.top() == q2.top()){
q1.pop();
q2.pop();
}
return;
}
int size(){
return q1.size() - q2.size();
}
int top(){
clean();
return q1.empty() ? -1 : q1.top();
}
void pop(){
clean();
if (!q1.empty()){
q1.pop();
}
return;
}
int get_two_max(){
if (size() < 2){
return -1;
}
int t1 = top();
pop();
int t2 = top();
push(t1);
return t1 + t2;
}
}s1[MAXN], s2[MAXN], ans;
void dfs_lca(int u, int p){
dep[u] = dep[p] + 1;
ord[++cur] = u;
in[u] = cur;
for (int v : adj[u]){
if (v != p){
dfs_lca(v, u);
ord[++cur] = u;
}
}
}
int get_min(int u, int v){
return (dep[u] < dep[v] ? u : v);
}
void init_st(){
lg[1] = 0;
for (int i = 2; i <= cur; i++){
lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= cur; i++){
st[i][0] = ord[i];
}
for (int i = 1; i <= lg[cur]; i++){
for (int j = 1; j <= cur - (1 << i) + 1; j++){
st[j][i] = get_min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
return;
}
int get_lca(int u, int v){
int l = in[u], r = in[v];
if (l > r){
swap(l, r);
}
int k = lg[r - l + 1];
return get_min(st[l][k], st[r - (1 << k) + 1][k]);
}
int get_dis(int u, int v){
int lca = get_lca(u, v);
return dep[u] + dep[v] - 2 * dep[lca];
}
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void build(int u, int p){
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
fa[rt] = p;
for (int v : adj[rt]){
if (!vis[v]){
build(v, rt);
}
}
return;
}
int get_ans(int i){
return s2[i].get_two_max();
}
void modify_tree(int i){
if (!flag[i]){
int tmp = get_ans(i);
if (tmp != -1){
ans.erase(tmp);
}
s2[i].push(0);
tmp = get_ans(i);
if (tmp != -1){
ans.push(tmp);
}
for (int u = i; fa[u]; u = fa[u]){
tmp = get_ans(fa[u]);
if (tmp != -1){
ans.erase(tmp);
}
if (s1[u].size() > 0){
s2[fa[u]].erase(s1[u].top());
}
s1[u].push(get_dis(i, fa[u]));
if (s1[u].size() > 0){
s2[fa[u]].push(s1[u].top());
}
tmp = get_ans(fa[u]);
if (tmp != -1){
ans.push(tmp);
}
}
flag[i] = true;
cnt++;
}
else{
int tmp = get_ans(i);
if (tmp != -1){
ans.erase(tmp);
}
s2[i].erase(0);
tmp = get_ans(i);
if (tmp != -1){
ans.push(tmp);
}
for (int u = i; fa[u]; u = fa[u]){
tmp = get_ans(fa[u]);
if (tmp != -1){
ans.erase(tmp);
}
if (s1[u].size() > 0){
s2[fa[u]].erase(s1[u].top());
}
s1[u].erase(get_dis(i, fa[u]));
if (s1[u].size() > 0){
s2[fa[u]].push(s1[u].top());
}
tmp = get_ans(fa[u]);
if (tmp != -1){
ans.push(tmp);
}
}
flag[i] = false;
cnt--;
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs_lca(1, 0);
init_st();
build(1, 0);
for (int i = 1; i <= n; i++){
modify_tree(i);
}
cin >> q;
while (q--){
char op;
cin >> op;
if (op == 'C'){
int i;
cin >> i;
modify_tree(i);
}
else{
if (cnt == 0){
cout << "-1\n";
}
else if (cnt == 1){
cout << "0\n";
}
else{
cout << ans.top() << "\n";
}
}
}
return 0;
}
:::
III
:::info[例题 3]{open}
P3345 [ZJOI2015] 幻想乡战略游戏
给定一棵有
6s,
:::
对于任意一个节点
直接在原树上做,最坏情况下要走
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector <pair<int, ll>> adj[MAXN], g[MAXN];
int n, q;
int dep[MAXN], ord[MAXN * 2], in[MAXN], lg[MAXN * 2], st[MAXN * 2][20], tim;
int siz[MAXN], fa[MAXN], cnt[MAXN], fi;
ll sum1[MAXN], sum2[MAXN], dis[MAXN];
bool vis[MAXN];
void init_lca(int u, int p){
dep[u] = dep[p] + 1;
ord[++tim] = u;
in[u] = tim;
for (auto [v, w] : adj[u]){
if (v == p){
continue;
}
dis[v] = dis[u] + w;
init_lca(v, u);
ord[++tim] = u;
}
return;
}
int get_min(int u, int v){
return dep[u] < dep[v] ? u : v;
}
void init_st(){
lg[1] = 0;
for (int i = 2; i <= tim; i++){
lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= tim; i++){
st[i][0] = ord[i];
}
for (int i = 1; i <= lg[tim]; i++){
for (int j = 1; j <= tim - (1 << i) + 1; j++){
st[j][i] = get_min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
return;
}
int get_lca(int u, int v){
int l = in[u], r = in[v];
if (l > r){
swap(l, r);
}
int k = lg[r - l + 1];
return get_min(st[l][k], st[r - (1 << k) + 1][k]);
}
int get_dis(int u, int v){
int lca = get_lca(u, v);
return dis[u] + dis[v] - 2 * dis[lca];
}
void dfs1(int u, int p){
siz[u] = 1;
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (auto [v, w] : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
int build(int u, int p){
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
fa[rt] = p;
vis[rt] = true;
for (auto [v, w] : adj[rt]){
if (!vis[v]){
int nxt = build(v, rt);
g[rt].push_back({v, nxt});
}
}
return rt;
}
void modify(int x, int val){
cnt[x] += val;
for (int u = x; fa[u]; u = fa[u]){
int p = fa[u], d = get_dis(x, p);
sum1[p] += 1ll * d * val;
sum2[u] += 1ll * d * val;
cnt[p] += val;
}
return;
}
ll query(int x){
if (!x){
return INF;
}
ll res = sum1[x];
for (int u = x; fa[u]; u = fa[u]){
int p = fa[u], d = get_dis(x, p);
res += sum1[p] - sum2[u];
res += 1ll * (cnt[p] - cnt[u]) * d;
}
return res;
}
ll find(int u){
ll val = query(u);
for (auto [v, nxt] : g[u]){
if (query(v) < val){
return find(nxt);
}
}
return val;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i < n; i++){
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
init_lca(1, 0);
init_st();
fi = build(1, 0);
while (q--){
int u, x;
cin >> u >> x;
modify(u, x);
cout << find(fi) << "\n";
}
return 0;
}
:::
IV
:::info[例题 4]{open}
P17141 [NOI 2026] 传送
给定一棵有
5s,
:::
可以发现以下几条性质:
- 传送会覆盖掉之前的所有移动,故我们一定不会移动几次后再传送。这意味着,我们的策略一定是让终点所在的某个连通块内的所有节点移动,其余节点传送;
- 钦定终点为根,则同深度节点选择的策略一定相同。结合性质 1,这意味着,以某个阈值
dep 为界,深度\le dep 的所有节点将选择移动,深度>dep 的所有节点将选择传送; - 传送一次后,期望步数就和起点没有任何关系了。这意味着,我们只需对每个终点
y 预处理传送一次后的期望步数P ,查询时将其与dis_{x} 取 min 即可。
记
继续观察性质。可以发现,若确定了节点
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 10;
vector <int> adj[MAXN], pre3[MAXN], pre4[MAXN];
vector <ll> pre1[MAXN], pre2[MAXN];
int a[MAXN], fa[MAXN], siz[MAXN], dep[MAXN], mxd1[MAXN], mxd2[MAXN], cur;
int ord[MAXN * 2], in[MAXN], st[MAXN * 2][20], lg[MAXN * 2];
ll pa[MAXN];
int pb[MAXN], pd[MAXN];
bool vis[MAXN];
void dfs_lca(int u, int p){
dep[u] = dep[p] + 1;
ord[++cur] = u;
in[u] = cur;
for (int v : adj[u]){
if (v != p){
dfs_lca(v, u);
ord[++cur] = u;
}
}
}
int get_min(int u, int v){
return (dep[u] < dep[v] ? u : v);
}
void init_st(){
lg[1] = 0;
for (int i = 2; i <= cur; i++){
lg[i] = lg[i / 2] + 1;
}
for (int i = 1; i <= cur; i++){
st[i][0] = ord[i];
}
for (int i = 1; i <= lg[cur]; i++){
for (int j = 1; j <= cur - (1 << i) + 1; j++){
st[j][i] = get_min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
return;
}
int get_lca(int u, int v){
int l = in[u], r = in[v];
if (l > r){
swap(l, r);
}
int k = lg[r - l + 1];
return get_min(st[l][k], st[r - (1 << k) + 1][k]);
}
int get_dis(int u, int v){
int lca = get_lca(u, v);
return dep[u] + dep[v] - 2 * dep[lca];
}
void dfs1(int u, int p){
siz[u] = 1;
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs1(v, u);
siz[u] += siz[v];
}
return;
}
void dfs2(int u, int p, int tot, int &rt){
int mx = tot - siz[u];
for (int v : adj[u]){
if (v == p || vis[v]){
continue;
}
dfs2(v, u, tot, rt);
mx = max(mx, siz[v]);
}
if (mx * 2 <= tot){
rt = u;
}
return;
}
void build(int u, int p){
dfs1(u, 0);
int rt = 0;
dfs2(u, 0, siz[u], rt);
vis[rt] = true;
fa[rt] = p;
for (int v : adj[rt]){
if (!vis[v]){
build(v, rt);
}
}
return;
}
void modify_tree(int x){
for (int u = x; u; u = fa[u]){
int d1 = get_dis(u, x), d2 = get_dis(fa[u], x);
pre1[u][d1] += d1;
pre3[u][d1]++;
if (fa[u]){
pre2[u][d2] += d2;
pre4[u][d2]++;
}
}
return;
}
pair <ll, int> query_tree(int x, int k){
int tmp = min(k, (int)pre1[x].size() - 1);
ll fi = pre1[x][tmp];
int se = pre3[x][tmp];
for (int u = x; fa[u]; u = fa[u]){
int d = get_dis(fa[u], x);
if (k >= d){
int t1 = min(k - d, (int)pre1[fa[u]].size() - 1);
int t2 = min(k - d, (int)pre2[u].size() - 1);
ll sum = pre1[fa[u]][t1] - pre2[u][t2];
int cnt = pre3[fa[u]][t1] - pre4[u][t2];
fi += sum + 1ll * d * cnt;
se += cnt;
}
}
return {fi, se};
}
void dfs3(int u, int p, int d, int n){
for (int v : adj[u]){
if (v == p){
continue;
}
for (int j = min(d + 1, n); j >= max(d - 1, 0); j--){
pair <ll, int> res = query_tree(v, j);
if (n + res.first > 1ll * j * res.second){
pa[v] = n + res.first;
pb[v] = res.second;
pd[v] = j;
dfs3(v, u, j, n);
break;
}
}
}
return;
}
vector <pair<ll, int>> teleport(int c, int n, int m, vector <int> u, vector <int> v, vector <int> x, vector <int> y){
for (int i = 0; i < n - 1; i++){
int ui = u[i] + 1;
int vi = v[i] + 1;
adj[ui].push_back(vi);
adj[vi].push_back(ui);
}
dfs_lca(1, 0);
init_st();
build(1, 0);
for (int i = 1; i <= n; i++){
for (int u = i; u; u = fa[u]){
mxd1[u] = max(mxd1[u], get_dis(u, i));
if (fa[u]){
mxd2[u] = max(mxd2[u], get_dis(fa[u], i));
}
}
}
for (int i = 1; i <= n; i++){
pre1[i].resize(mxd1[i] + 5, 0);
pre2[i].resize(mxd2[i] + 5, 0);
pre3[i].resize(mxd1[i] + 5, 0);
pre4[i].resize(mxd2[i] + 5, 0);
}
for (int i = 1; i <= n; i++){
modify_tree(i);
}
for (int i = 1; i <= n; i++){
for (int j = 1; j < (int)pre1[i].size(); j++){
pre1[i][j] += pre1[i][j - 1];
pre3[i][j] += pre3[i][j - 1];
}
for (int j = 1; j < (int)pre2[i].size(); j++){
pre2[i][j] += pre2[i][j - 1];
pre4[i][j] += pre4[i][j - 1];
}
}
int l = 0, r = n;
while (r - l >= 5){
int mid = (l + r) >> 1;
pair <ll, int> res = query_tree(1, mid);
if (n + res.first > 1ll * mid * res.second){
l = mid;
}
else{
r = mid;
}
}
for (int j = r; j >= l; j--){
pair <ll, int> res = query_tree(1, j);
if (n + res.first > 1ll * j * res.second){
pa[1] = n + res.first;
pb[1] = res.second;
pd[1] = j;
break;
}
}
dfs3(1, 0, pd[1], n);
vector <pair<ll, int>> ans(m);
for (int i = 0; i < m; i++){
int xi = x[i] + 1;
int yi = y[i] + 1;
if (pa[yi] < 1ll * get_dis(xi, yi) * pb[yi]){
ll t = gcd(pa[yi], pb[yi]);
ans[i] = {pa[yi] / t, (int)(pb[yi] / t)};
}
else{
ans[i] = {get_dis(xi, yi), 1};
}
}
return ans;
}
:::