之前的记录:
点分治可以处理一些普通的 tree dp 或者是树剖无法处理的树上路径问题。
简单说就是在一棵树上做分治,考虑对于一个固定根的树,路径只有可能经过根节点,或者完全在它的子树中。后者可以分治递归下去解决。为了保证递归层数是logn 的。我们每次选子树树的重心作为子树的新根。这样节点总数就减少了一半。
我们一般都会考虑无限制的经过根节点的情况(不考虑有一个更深的LCA),减去它完全在它子树中的情况。这种做法类似容斥原理的思路。无限制的经过根节点的情况具体问题具体分析,本题只需要考虑子树内的点对就好。对于一个树,O(N)找到每个子孙节点到rt的距离。放到一个数组里面,排个序,双指针扫描一下就好了。复杂度O(n(logn)^2)
注意在求子树大小siz的时候不要在外面memset。这会非常的慢,达到n^2.在里面令siz[x]一开始为1就好了。
luoguP4178
luoguP4149
给定一棵树,问长度为k的路径的最小边数。
看到了路径与长度有关,套一个点分治。假设当前固定了一个点,为了处理LCA不是它的情况,有三种方法。
1:对于操作可以抵消的情况,先统计 x x x这个根,只考虑它的子树的一些点的贡献。然后减去它孩子为根的答案。再递归到它的儿子。
2:如果操作不可以抵消,可以给每个子树染色。只统计颜色不一样的答案。
3:如果染色也不方便,可以按顺序处理子树。用什么东西把前面子树的信息保存下来,然后这个直接查就行了。 注意处理完一个以它为根的,要把这一轮保存的值清空。
这一题采用第三种方法,用桶来存值。枚举每个子树,先查再插。O(nlogn)
#include <bits/stdc++.h>// id from 0
using namespace std;//bar就是那个桶
const int N = 2e5 + 5, K = 1e6 + 5, INF = 0x3f3f3f3f;
int n, k, cnt, las;
int head[N], nxt[N<<1], v[N<<1], w[N<<1], tot;
int used[N], siz[N], dis1[N], dis2[N], bar[K];
int rt, Minsiz, all, ans = INF;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
inline void add(int x, int y, int z) {
v[++tot] = y;
w[tot] = z;
nxt[tot] = head[x];
head[x] = tot;
}
inline void getrt(int x, int fa) {
siz[x] = 1;
int mx = 0;
for (int i = head[x]; i; i = nxt[i]) {
if (v[i] != fa && !used[v[i]]) {
getrt(v[i], x);
siz[x] += siz[v[i]];
mx = max(mx, siz[v[i]]);
}
}
mx = max(mx, all - siz[x]);
if (mx < Minsiz) {
Minsiz = mx;
rt = x;
}
}
inline void getdis(int x, int fa, int w1, int w2) {
if (w1 > k) return ;
dis1[++cnt] = w1; dis2[cnt] = w2;
for (int i = head[x]; i; i = nxt[i]) {
if (used[v[i]] || v[i] == fa) continue;
getdis(v[i], x, w1 + w[i], w2 + 1);
}
}
inline void calc(int x) {
bar[0] = 0; cnt = 0;
for (int i = head[x]; i; i = nxt[i]) {
if (used[v[i]]) continue;
las = cnt;
getdis(v[i], x, w[i], 1);
for (int j = las + 1; j <= cnt; ++j) ans = min(ans, bar[k - dis1[j]] + dis2[j]);
for (int j = las + 1; j <= cnt; ++j) bar[dis1[j]] = min(bar[dis1[j]], dis2[j]);
}
for (int i = 1; i <= cnt; ++i) bar[dis1[i]] = INF;
}
inline void solve(int x) {
used[x] = 1; calc(x); //printf("x = %d\n", x);
for (int i = head[x]; i; i = nxt[i]) {
if (!used[v[i]]) {
all = siz[v[i]]; Minsiz = INF;
getrt(v[i], x); solve(rt);
}
}
}
int main() {
//freopen("luoguP4149.in", "r", stdin);
//freopen("try.out", "w", stdout);
n = read(); k = read();
for (int i = 1; i < n; ++i) {
int x = read(), y = read(), z = read();
add(x, y, z); add(y, x, z);
}
memset(bar, 0x3f, sizeof bar);
all = n;
Minsiz = INF; getrt(0, 0);
solve(rt);
if (ans == INF) puts("-1");
else printf("%d\n", ans);
return 0;
}