[模板] 树的重心/点分治/动态点分治

树的重心

树的其他问题 - OI Wiki

定义

\((1)\) 树的重心 \(\leftrightarrow\) 所有的子树中最大的子树节点数最少的节点.

\((2)\) 树的重心 \(\leftrightarrow\) 所有子树的大小都不超过整个树大小的一半的节点.

性质

删去重心后, 生成的多棵树尽可能平衡.

一棵树中最少有两个重心.

树中所有点到某个点的距离和中, 到重心的距离和是最小的; 如果有两个重心, 那么他们的距离和一样.

把两个树通过一条边相连得到一个新的树, 那么新的树的重心在连接原来两个树的重心的路径上.

把一个树添加或删除一个叶子, 那么它的重心最多只移动一条边的距离.

求法&&代码

根据定义\((1)\), dfs求即可.

int szp[nsz],sum,maxp[nsz]{2e4+5},rt=0;

void getrt(int p,int fa){
    szp[p]=1,maxp[p]=0;
    forg(p,i,v){
        if(v==fa)continue;
        getrt(v,p);
        szp[p]+=szp[v];
        maxp[p]=max(maxp[p],szp[v]);
    }
    maxp[p]=max(maxp[p],sum-szp[p]);
    if(maxp[p]<maxp[rt])rt=p;
}

点分治

简介

点分治可以解决一些树上的路径问题.

对于一棵无根树, 可以将一个点 \(rt\) 设为根, 把它转化为有根树. 那么树上的路径可以分为两类:

  1. 经过根的路径;
  2. 仅在某个子树内的路径.

对于1, 我们可以将链 \((a,b)\) 分为 \((a,rt)\), \((rt,b)\), 多数情况可以合并他们的影响 (例如求路径长度). 一般有两种做法:

  • 处理 \(rt\) 及其所有子节点, 求出两两之间的对答案的贡献, 再减去同一子树内的点对对答案的贡献.

  • 分别递归 \(rt\) 的每个子树, 对每个子树求出其与之前的子树中的节点形成点对的贡献. 这样就不需要容斥减去重复的部分.

对于2, 可以递归求解.

可以证明, 如果在递归每个子树时都将子树的重心设为根, 那么递归层数是 \(O(\log n)\) 的.

代码

//store the tree
const int nsz=20050;

ll n;
struct te{int t,pr,v;}edge[nsz*2];
int hd[nsz],pe=1;
void adde(int f,int t,int v){edge[++pe]=(te){t,hd[f],v};hd[f]=pe;}
void adddb(int f,int t,int v){adde(f,t,v);adde(t,f,v);}
#define forg(p,i,v) for(int i=hd[p],v=edge[i].t;i;i=edge[i].pr,v=edge[i].t)

//find center of tree
int vi[nsz];
int szp[nsz],sum,maxp[nsz]{2e4+5},rt;

void getrt(int p,int fa){
    szp[p]=1,maxp[p]=0;
    forg(p,i,v){
        if(vi[v]||v==fa)continue;
        getrt(v,p);
        szp[p]+=szp[v];
        maxp[p]=max(maxp[p],szp[v]);
    }
    maxp[p]=max(maxp[p],sum-szp[p]);
    if(maxp[p]<maxp[rt])rt=p;
}

void sol1(int rt,int v0,int fl){
//do something
}

void divide(int p){
    int sum0=sum;
    vi[p]=1;
    sol1(p,0,1);
    forg(p,i,v){
        if(vi[v])continue;
        sol1(v,edge[i].v,-1);
        rt=0,sum=sum0-maxp[v],getrt(v,0);
        divide(rt);
    }
}

ll sol(){
    rt=0,sum=n,getrt(1,0); //init
    divide(rt);
    return ans;
}

例题

bzoj2152-[国家集训队]聪聪可可

动态点分治

转载于:https://www.cnblogs.com/ubospica/p/10408644.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值