点分树学习笔记
点分树是基于点分治的支持修改操作的算法,所以也叫动态点分治。
具体来说,我们发现对于某些树上的题目,点与点之间的距离十分重要,点与点之间的关系反而不那么重要。比如查询距离 的点。
由于点分治是一种离线做法,而很多毒瘤出题人往往会加上修改/强制在线,这时候就用到点分树了。
考虑点分治之后我们将每个点分中心连向它的上级点分中心。可以发现,由于点分治的性质,这样建出来的树的树深一定是 的。
这意味着我们可以跳 fa 把一个暴力做法优化到 。
但是这样会有一个问题:如果我们对每个点分中心(及其祖先)都记录,那么势必会有一些点会被计算多次。
比如上图显然点分治中心是 1,然后二层分治中心是 2,5。可以发现,如果我们查询距离4号点 4以内的点,那么首先会查询距离2号点3以内的点,再查询距离1号点2以内的点。可以发现这里就会被记重。
比如答案英应该是蓝色区域与红色区域的并,但是我们会把交统计两遍。
所以考虑容斥。不妨专门开一个数组统计点分树上的父亲在当前点分子树的贡献,即上图中蓝色区域与红色区域的交。
我们把所有答案加起来,再去掉这部分贡献即可。
模板:bzoj3730震波
题目大意
给定一棵树,点有点权,支持单点修改,查询距离某一点 不超过 的点权和。
题解
建出点分树。可以发现,我们要做的就是单点修改,维护前缀和。
这个直接用线段树/树状数组维护一下即可。特别的,这里需要用到上述差分技巧,即 应当减去已经处理过的子树 的贡献。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
int nxt[N<<1],to[N<<1],head[N],cnt;
void add(int u,int v)
{
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}
namespace ST{
int f[21][N*2],dep[N*2],id[N*2],tot;
int _2[N*2];
void dfs(int u,int p)
{
dep[u]=dep[p]+1;
f[0][++tot]=u;
id[u]=tot;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==p) continue;
dfs(v,u);
f[0][++tot]=u;
}
}
int lca(int x,int y)
{
x=id[x],y=id[y];
if(x>y) swap(x,y);
int p=_2[y-x+1],u=1<<p;
return dep[f[p][x]]>dep[f[p][y-u+1]]?f[p][y-u+1]:f[p][x];
}
void init()
{
dfs(1,0);
for(int i=2;i<=tot;i++) _2[i]=_2[i>>1]+1;
for(int i=1,p=2;p<=tot;i++,p<<=1)
for(int j=1;j+p-1<=tot;j++)
if(dep[f[i-1][j]]<dep[f[i-1][j+p/2]]) f[i][j]=f[i-1][j];
else f[i][j]=f[i-1][j+p/2];
}
}
int dist(int x,int y){return ST::dep[x]+ST::dep[y]-2*ST::dep[ST::lca(x,y)];}
int ssiz[N],maxs[N],rt;
bool vis[N];
void dfs1(int u,int f,int all)
{
ssiz[u]=1;
maxs[u]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v] || v==f) continue;
dfs1(v,u,all);
maxs[u]=max(maxs[u],ssiz[v]);
ssiz[u]+=ssiz[v];
}
maxs[u]=max(maxs[u],all-ssiz[u]);
if(!rt || maxs[u]<maxs[rt]) rt=u;
}
int siz[N],fa[N];
void dfs2(int u,int all)
{
vis[u]=true;
siz[u]=all;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v]) continue;
int vz=ssiz[v]<ssiz[u]?ssiz[v]:all-ssiz[u];
rt=0;
dfs1(v,u,vz);
fa[rt]=u;
dfs2(rt,vz);
}
}
int root[N],droot[N],ls[N*40],rs[N*40],tcnt,val[N*40];
void insert(int &u,int l,int r,int p,int v)
{
if(!u) u=++tcnt;
val[u]+=v;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) insert(ls[u],l,mid,p,v);
else insert(rs[u],mid+1,r,p,v);
}
int answer(int u,int l,int r,int L,int R)
{
if(L<=l && r<=R) return val[u];
if(!u || L>R) return 0;
int mid=(l+r)>>1,a=0;
if(L<=mid) a+=answer(ls[u],l,mid,L,R);
if(R>mid) a+=answer(rs[u],mid+1,r,L,R);
return a;
}
void change(int s,int v)
{
for(int p=s;p;p=fa[p])
{
insert(root[p],0,siz[p],dist(s,p),v);
if(fa[p]) insert(droot[p],0,siz[fa[p]],dist(fa[p],s),v);
}
}
int answer(int s,int k)
{
int res=0;
for(int u=s,p=0;u;p=u,u=fa[u])
{
int d=dist(u,s);
if(d>k) continue;
res+=answer(root[u],0,siz[u],0,k-d);
if(p) res-=answer(droot[p],0,siz[u],0,k-d);
}
return res;
}
int w[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
ST::init();
dfs1(1,0,n);
dfs2(rt,n);
for(int i=1;i<=n;i++) change(i,w[i]);
int las=0;
for(int i=1;i<=m;i++)
{
int opt,x,y;
scanf("%d%d%d",&opt,&x,&y);
x^=las,y^=las;
if(opt==0) printf("%d\n",las=answer(x,y));
else change(x,y-w[x]),w[x]=y;
}
return 0;
}
本文来自博客园,作者:Flying2018,转载请注明原文链接:https://www.cnblogs.com/Flying2018/p/13447053.html
标签:
学习笔记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理