通过这个blog入门的:https://www.luogu.org/blog/user9012/dian-fen-zhi-lve-xie
在做一类树上的题目时,往往需要对树进行分治,能将子树分解成大小尽量相等的情况是最吼的,因此我们每次选择根节点的时候都应该是当前部分的重心.
因此点分治模板包含了两个主要的过程:寻找当前部分的根和点分治主递归.
1. s i z [ u ] siz[u] siz[u],表示子树大小.
2. u s e [ u ] = 1 use[u] = 1 use[u]=1的点不能访问,这限制了重心只能在当前部分寻找.
3. b i g [ u ] big[u] big[u],表示以 u u u为根节点最大子树大小.
4. r t rt rt,表示当前部分的重心.
5. a l l all all,表示当前部分的大小.
int siz[N],use[N],big[N],rt,all;
void getroot(int u,int fa) {
siz[u] = 1;big[u] = 0;
for(auto p : G[u]) {
int v = p.first;
if(use[v] || v == fa) continue;
getroot(v,u);
siz[u] += siz[v];
big[u] = std::max(big[u],siz[v]);
}
big[u] = std::max(big[u],all-siz[u]);
if(big[u] < big[rt]) rt = u;
}
代码实现比较简单,很容易理解.
void dfs(int u) {
//calc过程是求解与u有关的问题.
use[u] = 1;calc(u);
for(auto p : G[u]) {
int v = p.first;
if(use[v]) continue;
rt = 0;all = siz[v];
//寻找子问题的重心,并递归处理
getroot(v,u);dfs(rt);
}
}
对 u u u的每颗子树记录一个 s e t set set,里面存这个子树中的点到 u u u节点的距离可能值.
对 u u u节点存一个 f i n fin fin,表示遍历过的子树中的点到 u u u点的可行距离.
每回溯完一颗子树后,先判断该子树能否与之前的子树形成距离为 k k k的点对,然后再把该子树的 s e t set set合并到 f i n fin fin中去.
代码只贴 c a l c calc calc部分
void calc(int u) {
std::unordered_set<int> fin;
fin.insert(0);
for(auto p : edge[u]) {
int v = p.first;
int c = p.second;
if(use[v]) continue;
std::unordered_set<int> child;
getdis(child,v,u,c);
for(auto x : child) {
rep(i,1,m) {
if(fin.count(ask[i] - x)) {
ans[i] = 1;
}
}
}
for(auto x : child)
fin.insert(x);
}
}
同上一题,将 s e t set set改为 m a p map map,其中 m a p [ i ] map[i] map[i]表示距离为 i i i的点有多少个即可.
void calc(int u) {
map fin;
fin[0] = 1;
for(auto v : G[u]) {
if(use[v]) continue;
map child;
getdis(child,v,u,1);
for(auto p : child) {
ans += fin[k-p.first] * p.second;
}
for(auto p : child) {
fin[p.first] += p.second;
}
}
}
还是使用 m a p map map来维护,只不过在计算答案的时候,需要用双指针来扫描.
void calc(int u) {
map fin;
fin[0] = 1;
for(auto pp : G[u]) {
int v = pp.first,c = pp.second;
if(use[v]) continue;
map child;
getdis(child,v,u,c);
int sum = 0;
auto itf = fin.begin();
for(auto itc = child.rbegin();itc != child.rend();++itc) {
while(itf != fin.end() && itf->first + itc->first <= k) {
sum += itf->second;
++itf;
}
ans += sum * itc->second;
}
for(auto p : child) {
fin[p.first] += p.second;
}
}
}
这题也很简单,仅仅需要修改一下 m a p map map表示的含义即可.
m a p [ i ] map[i] map[i]表示距离为 i i i的路径最少边数.
void calc(int u) {
map fin;
for(auto pp : G[u]) {
int v = pp.first,c = pp.second;
if(use[v]) continue;
map child;
getdis(child,v,u,c,1);
for(auto p : child) {
if(p.first == k)
ans = std::min(ans,p.second);
else if(fin[k-p.first])
ans = std::min(ans,fin[k-p.first]+p.second);
}
for(auto p : child) {
if(fin[p.first] == 0)
fin[p.first] = p.second;
else
fin[p.first] = std::min(fin[p.first],p.second);
}
}
}