静态点分治学习笔记

  • 处理树上路径问题。
  • 填以前的坑。

    静态点分治。

  • 点分治的核心思想就是分治,每次选取树的重心把树分成两个部分。
  • 在这里,树的重心的定义是指以他为根,最大子树\(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

题单:

猜你喜欢

转载自www.cnblogs.com/Tyher/p/9790459.html
今日推荐