点分树
黄昏夕阳,铺一片余晖锦缎,远处炊烟袅袅,我们活在这美好温暖的人间。
本文是基于辰星凌的博客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;
}

浙公网安备 33010602011771号