点分治笔记
解决一类树上路径问题的方法
树的重心
大概就是物理意义上的质心,让一棵树尽可能平衡..
对于一个节点,计算其所有子树中最大的节点数,该值最小的节点就是树的重心
模板 点分治
给定一棵有 个点的树,询问树上距离为 的点对是否存在。
指定一个根 ,影响的就是在其子树中并且经过 的路径,这些路径考虑完就可以排除它的影响往下递归
枚举 的所有子节点 ,记 为节点 到 的距离, 表示之前处理的子树是否存在 ,然后检查 即可
清空 的时候只把出现过的清空,不直接memset以保证复杂度
点分治过程假设递归了 层,复杂度为 ,因此每次选择树的重心,保证在
也就亿点点难写而已..
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
const int M = 110;
int n, m;
int rt, sum;
int siz[N], weight[N], q[M];
bool vis[N], ans[N], tf[10000010];
vector<pair<int, int>> adj[N];
void getRoot(int u, int fa) {
siz[u] = 1, weight[u] = 0;
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
getRoot(v, u);
weight[u] = max(weight[u], siz[v]);
siz[u] += siz[v];
}
weight[u] = max(weight[u], sum - siz[u]);
if (weight[u] < weight[rt]) rt = u;
}
int d[N], dis[N], cnt;
void getDis(int u, int fa) {
dis[++cnt] = d[u];
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
d[v] = d[u] + w, getDis(v, u);
}
}
queue<int> del;
void dfs(int u, int fa) {
tf[0] = 1;
del.push(0);
vis[u] = 1;
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
d[v] = w;
getDis(v, u);
for (int k = 1; k <= cnt; ++k)
for (int i = 1; i <= m; ++i)
if (q[i] >= dis[k])
ans[i] |= tf[q[i] - dis[k]];
for (int k = 1; k <= cnt; ++k)
if (dis[k] <= 1e7)
del.push(dis[k]), tf[dis[k]] = 1;
cnt = 0;
}
while (!del.empty())
tf[del.front()] = 0, del.pop();
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
sum = siz[v];
rt = 0;
weight[rt] = INT_MAX;
getRoot(v, u);
getRoot(rt, -1); // 计算siz
dfs(rt, u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
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 >> q[i];
rt = 0, sum = n, weight[rt] = INT_MAX;
getRoot(1, -1);
getRoot(rt, -1); // 计算siz
dfs(rt, -1);
for (int i = 1; i <= m; ++i)
if (ans[i] == 1)
cout << "AYE\n";
else
cout << "NAY\n";
return 0;
}
洛谷 P4178 Tree
男人八题之一,十八年前的神仙题现在已经飞入寻常百姓家了..
给定一棵 个节点的树,每条边有边权,求出树上两点距离小于等于 的点对数量。
变成了点修区间查,用线段树或者树状数组搞一搞就好了
值得注意的是因为维护值域的话下标为 0 不是很好处理,所以所有值 +1 处理
#include <bits/stdc++.h>
using namespace std;
const int N = 40010;
const int K = 20010;
int n, k;
int rt, sum;
int siz[N], weight[N];
bitset<N> vis;
vector<pair<int, int>> adj[N];
void getRoot(int u, int fa) {
siz[u] = 1, weight[u] = 0;
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
getRoot(v, u);
siz[u] += siz[v];
weight[u] = max(weight[u], siz[v]);
}
weight[u] = max(weight[u], sum - siz[u]);
if (weight[u] < weight[rt])
rt = u;
}
int d[N], dis[N], cnt;
void getDis(int u, int fa) {
dis[++cnt] = d[u];
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
d[v] = d[u] + w, getDis(v, u);
}
}
queue<int> tag;
int c[K];
void mark(int x, int v) {
for (; x <= K; x += x & -x) c[x] += v;
}
int query(int x) {
int res = 0;
for (; x; x -= x & -x) res += c[x];
return res;
}
queue<int> del;
int ans;
void dfz(int u, int fa) {
// cout << "root " << u << '\n';
mark(1, 1);
del.push(1);
vis[u] = 1;
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
d[v] = w;
getDis(v, u);
for (int i = 1; i <= cnt; ++i)
if (k >= dis[i])
ans += query(k - dis[i] + 1);
for (int i = 1; i <= cnt; ++i)
if (dis[i] <= k)
mark(dis[i] + 1, 1), del.push(dis[i] + 1);
cnt = 0;
}
// for (int i = 1; i <= n; ++i)
// cout << "d[" << i << "] " << d[i] << '\n';
// cout << query(k + 1) << "\n\n";
while (!del.empty())
mark(del.front(), -1), del.pop();
cnt = 0;
for (auto &[v, w] : adj[u]) {
if (v == fa || vis[v])
continue;
sum = siz[v];
rt = 0;
weight[rt] = INT_MAX;
getRoot(v, u);
getRoot(rt, -1);
dfz(rt, u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
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;
ans = 0;
rt = 0;
sum = n;
weight[rt] = INT_MAX;
getRoot(1, -1);
getRoot(rt, -1);
dfz(rt, -1);
cout << ans << '\n';
return 0;
}
动态点分、、 这玩意儿先不搞,这种就和整体二分、复杂莫队一样,考场金牌题,平时训练又是一回事