- 处理树上路径问题。
- 填以前的坑。
静态点分治。
-
点分治的核心思想就是分治,每次选取树的重心把树分成两个部分。
-
在这里,树的重心的定义是指以他为根,最大子树(sz)最小。
-
然后每次划分就只考虑经过树的重心的路径。
-
因为每次划分都至少把树分成一半,所以复杂度就是(log)了。
-
得到重心:
void Getroot(R i,R fm){
sz[i]=1;R num=0;
for(R k=hd[i];k;k=nt[k]){
if(to[k]==fm||vis[to[k]])continue;
Getroot(to[k],i),sz[i]+=sz[to[k]];
num=max(num,sz[to[k]]);
}
num=max(num,tot-sz[i]);
if(num<Max)Max=num,root=i;
}
- 注意这里的重心是划分出来的一个树,应该可以理解成一个联通块。
- (vis)就是已经划分过的割点,不能走了。
- (num)表示最大的子树,(tot)是这个子树大小。
num=max(num,tot-sz[i]);
- 注意到这一句,这个以这个点为根的子树最大值,是本来的子树大小,和联通块大小减去子树大小的最大值。
- 然后所有的点取最小的点即可。
第一种写法:
- 跳点分治树:
void Dfs(R i,R fm){
sol(i,0,1),vis[i]=1;
for(R k=hd[i];k;k=nt[k]){
if(to[k]==fm||vis[to[k]])continue;
sol(to[k],w[k],-1),tot=sz[to[k]],Max=inf;
Getroot(to[k],0),Dfs(root,0);
}
}
- (sol)的含义就是求解答案,每个题目不一样。
- 因为现在的(i)一定是重心,(vis[i]=1)就相当于把树隔开了。
- 然后枚举所有的儿子,再把一个儿子中的贡献减去。
注意:
- 我们考虑的是经过重心的路径,但是可以发现,对于一个重心(i),两个点在同一个子树内,那么路径是不会经过(i)的。然后我们单独把这个子树内的答案剪掉。注意,此时每个点的初始长度都是(w_k),这样才可以正确剪掉原来被重复统计的路径。
- 然后初始化(tot),(Max),得到每一个子树根,然后再跳点分树。
- 每一次统计答案可能要清空。
- 例题 P2634 [国家集训队]聪聪可可
第二种写法:
- 对于最大值/最小值,不好删去重复的贡献(方案类问题是很好删除的,就是直接剪掉即可,但是最大值你不好删掉,因为不好维护次大值。)
- 跳点分树
void Dfs(R i){
vis[i]=1;
for(R k=hd[i];k;k=nt[k])
if(!vis[to[k]])go(to[k],i,1,w[k]),let(to[k],i,1,w[k]);
for(R k=hd[i];k;k=nt[k])
if(!vis[to[k]])emp(to[k],i,w[k]);
for(R k=hd[i];k;k=nt[k]){
if(vis[to[k]])continue;
tot=sz[to[k]],rt=0,Mx=n+1;
Getrt(to[k],0),Dfs(rt);
}
}
- 同样的,把树隔开。
- 然后对于每一个子树,先考虑他和之前的点两两组合得到的答案((go)函数)
- 然后在保存这个子树中一个答案产生的贡献((let)函数)
- 最后清除筒((emp)函数)
- 然后再分重心。
- 例题:P4149 [IOI2011]Race
题单:
- [x] 点分治1
- [x] Tree
- [x] [国家集训队]聪聪可可
- [x] [IOI2011]Race
- 前面四道都是模板题。
- [ ] [Luogu2664]树上游戏
- [ ] [WC2010]重建计划
- [ ] [HDU4812]D Tree
- [ ] [SPOJ1825]Free tour II
- [ ] [HDU5977]Garden of Eden
- [ ] [HDU5909]Tree Cutting
- [ ] [[Luogu3727]曼哈顿计划E]]
- [ ] P2993 最短路径树问题