点分树

黄昏夕阳,铺一片余晖锦缎,远处炊烟袅袅,我们活在这美好温暖的人间。

本文是基于辰星凌的博客QAQ大佬的博客的自己的一些“摘抄”和自己的一些想法

点分治的核心思想在于依据重心划分子连通块,按照分治递归的顺序提一颗新树出来对于每一个找到的重心,将上一层分治时的重心设为它的父亲,得到一颗大小不变、最多 logn 层的重构树


「HNOI2015」开店

维护一颗带点权、边权树,每次给出 \(x,l,r\),查询 \(∑_{l⩽Ay⩽r}dis(x,y)\),其中 \(Ay\) 为 \(y\) 的点权。

\(f1(i,j)=∑_{x∈subtree(i),Ax⩽j}dis(x,i)\)

\(f2(i,j)=∑_{x∈subtree(i),Ax⩽j}dis(x,fai)\)

\(sz(i,j)=∑_{x∈subtree(i),Ax⩽j}1\)

\(G(i,fa_i)= f1(fa_i,k-dis(x,fa_i))−f2(i,k-dis(x,fa_i))+dis(x,fa_i)×(sz(fa_i,k)−sz(i,k))\)

\(f1\)多计算了\(i\)子树内部的,所以减去\(f2\) \(i\)子树内部据\(fa_i\)满足\(f1\)条件的(也就是多算的)

总答案=\(fa_i-i\)子树下距\(fa_i\)小于等于\(k-dis(x,fa_i)\)的+\(dis(x,fa_i)\times\)满足条件点的个数

\(ans(x,k)=f1(x,k)+∑_{i∈fatree(x),fa_i≠0}G(i,fa_i)\)

void solve(ll u){
    dfs1(u);//get size
    if(sz[u]==1){
        cut[u]=1;
        fa[u].push_back({u,0,-1});
        return;
    }//边界条件
    Size=sz[u],mn=INF;
    dfs2(u);//找 重心 
    cut[Rt]=1;//重心已经被删掉 
    fa[Rt].push_back({Rt,0,-1});
    for(ll i=hd[Rt],t=0;i;i=nxt[i]){
        ll y=to[i];
        if(cut[y])continue;//之前已经作为重心被使用过了
        d[y]=val[i];
        dfs3(y,Rt,t);//预处理vector
        son[Rt][t].push_back({INF,0,0});//
        sort(son[Rt][t].begin(),son[Rt][t].end());//按点权排序 
        for(ll j=son[Rt][t].size()-2;j>=0;j--){
            son[Rt][t][j].sz+=son[Rt][t][j+1].sz;//处理后缀和
            son[Rt][t][j].dis+=son[Rt][t][j+1].dis;
        }
        t++;
    }
    for(ll i=hd[Rt];i;i=nxt[i]){
        ll y=to[i];
        if(!cut[y])solve(y);
    }//递归点分治
}

「BZOJ3730」震波

以下用 \(fa_i\) 表示点 \(i\) 在虚树上的父亲,\(subtree(i)\) 为点 \(i\) 在虚树上的子树集合,\(fatree(i)\) 为点 \(i\) 在虚树上的祖先集合,\(dis(i,j)\) 为 \(i,j\) 两点在原树上的距离,\(Ai\) 为点 \(i\) 的点权。

\(f1(i,j)=∑_{x∈subtree(i),dis(x,i)⩽j}Ax\)

即虚树上 i 的子树中与 i 距离小于等于 j 的点权值之和

\(f2(i,j)=∑_{x∈subtree(i),dis(x,fai)⩽j}Ax\)

即虚树上 i 的子树中与 \(fa_i\) 距离小于等于 \(j\) 的点权值之和

在一次查询 \((x,k)\) 中,对于虚树上的一对父子节点 \((i,fa_i)\)\(subtree(fa_i)−subtree(i)\) 对答案的贡献为 \(G(i,fa_i)=f1(fa_i,k−dis(x,fa_i))-f2(i,k−dis(x,fa_i)) \)

\(f1(fa_i,k−dis(x,fa_i))\)包含了多余的i子树的部分

因此后面减去\(f2(i,k−dis(x,fa_i))\) i子树小于等于k−dis(x,fa_i)的部分

\(ans(x,k)=f1(x,k)+ ∑_{i∈fatree(x),fa_i≠0}G(i,fa_i)\)

注意前面那个 \(f1(x,k)\)是因为容斥求和后没有被统计进去,所以要单独算

有一个问题就是:

void solve(int x,int F){//处理重心x所囊括的连通块
    int now=Size;
    vis[x]=1;
    fa[x]=F;
    f1[x].build(now/2+1);
    f2[x].build(now+1);//这个地方大小为什么是now?为什么不是now/2?
    for(int i=hd[x];i;i=a[i].nxt){
        int y=a[i].to;
        if(vis[y]) continue;
        Size=sz[y]>sz[x]?now-sz[x]:sz[y];//注意子连通块大小不要直接用sz[y]
        rt=0;
        mx[rt]=INF;
        getrt(y,0);
        solve(rt,x);
    }    
}

\(Attention\)
在 \(subtree(i)\) 中为重心

所以 \(f1(i,j)\) 中 \(j\) 的值域为 \([0∼now/2]\)

\(f2(i,j)\) 中 \(j\) 的值域为 \([1∼now]\)

[2010国家集训队]Crash的旅游计划

就是和上面那道题差不多了啦~(呕🤢)

题意就是给出 \(n\) 个点的一棵树,对于每个点求出距离ta第 \(k\) 小的距离

动态点分治
我们可以先建出点分树
然后二分一个距离 \(k\),判断其他点到这个点的距离小于等于这个值的有几个
那么现在的问题就是给出一个点,求出到这个点的距离不大于\(k\)的有几个点
\(f1[u]\) 数组表示 \(u\) 的子树内的点到 \(u\) 的距离,\(f2[u]\)表示\(u\)的子树内的点到 \(fa[u]\) 的距离
然后每次查询点u的时候就顺着点分树往上跳,期间查询所有的点到点\(u\)的距离不超过\(k\)的点
注意每次往上跳到 \(fa[u]\) 再查 \(fa[u]\) 的子树的时候会把已经统计过的 \(u\) 的子树的信息重复统计一部分,所以我们要容斥掉u的子树内的信息

\(Attention\)

往上跳的时候如果跳到点\(x\)使得\(dis(u,x)>k\)也不要直接 \(break\)

而是要继续往上跳,因为点分树是将原树重构了

可能\(dis(u,x)>k\)了但是\(x\)的祖先到\(u\)的距离\(≤k\)


void dfs1(int x,int fa){
    ...//求重心
}
void dfs2(int x,int tp){
    ...//重链剖分
}
int lca(int x,int y) {
    ...//重链剖分求LCA
}
int dist(int x,int y) {
	...//原树两点之间距离
}
void getrt(int x,int fa){//获取该连通块的重心
    sz[x]=1,mx[x]=-INF;
    for(int i=hd[x];i;i=e[i].nxt){
    	int y=e[i].to;
    	if(vis[y]||y==fa) continue;
    	getrt(y,x);
		sz[x]+=sz[y];
		mx[x]=max(mx[x],sz[y]);
    }
    mx[x]=max(mx[x],Size-sz[x]);
    if(mx[x]<mx[rt])rt=x;
} 
void solve(int x,int F){
    fa[x]=F; 
	vis[x]=1;
    for(int i=hd[x];i;i=e[i].nxt) {
        int y=e[i].to; 
		if(vis[y]) continue;
        Size=sz[y]; rt=0;mx[rt]=INF;
        getrt(y,x); 
		solve(rt,x);
    }
}
void update(int x) {
    f1[x].push_back(0) ;
    for(int i=x;fa[i];i=fa[i]) {
    	int tmp=dist(x,fa[i]);
        f1[fa[i]].push_back(tmp);
        f2[i].push_back(tmp);
    }
}
int query(int id,int x,int k) {
    if(id==1){
        int l=0,r=f1[x].size()-1,res=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(f1[x][mid]<=k){
            	l=mid+1;
				res=mid;
            }else r=mid-1;
        }
        return res+1;
    }else{
        int l=0,r=f2[x].size()-1,res=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(f2[x][mid]<=k){
            	l= mid+1;
				res=mid;
            }else r=mid-1;
        }
        return res+1;
    }
}
int check(int x,int k){
    int ans=query(1,x,k);
    for(int i=x;fa[i];i=fa[i]){
        int tmp=dist(x,fa[i]); 
		if(k<tmp)continue;
        ans+=query(1,fa[i],k-tmp)-query(2,i,k-tmp);
    }
    return ans-1;
}
int erfen(int i){
	int l=1,r=INF,res=0;
    while(l<=r){
        int mid=(l+r)>>1 ;
        if(check(i,mid)>=k){
			res=mid;
			r=mid-1;
		}else l=mid+1;
    }
    return res;
}
int main(){
	scanf("%s",s) ; 
	scanf("%d%d",&n,&k);
    for(int i=1,x,y,w;i<n;i++){
        scanf("%d%d%d",&x,&y,&w);
        add(x,y,w),add(y,x,w);
    }
   	d[1]=1; 
    dfs1(1,0);
	dfs2(1,1); 
    Size=n;rt=0;mx[rt]=INF;
	getrt(1,0); 
	solve(rt,0) ;
    for(int i=1;i<=n;i++){
    	update(i);
    }
    for(int i=1;i<=n;i++){
        sort(f1[i].begin(),f1[i].end());
        sort(f2[i].begin(),f2[i].end());
    }
    for(int i=1;i<=n;i++){
        printf("%d\n",erfen(i));
    }
    return 0;
}
posted @ 2023-01-12 10:17  _Youngxy  阅读(54)  评论(0)    收藏  举报