静态点分治学习笔记
- 处理树上路径问题。
- 填以前的坑。
静态点分治。
-
点分治的核心思想就是分治,每次选取树的重心把树分成两个部分。
-
在这里,树的重心的定义是指以他为根,最大子树\(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
题单:
- 前面四道都是模板题。