首页 > 其他分享 >点分树 学习笔记

点分树 学习笔记

时间:2024-07-16 20:19:00浏览次数:8  
标签:子树 重心 int 分治 笔记 学习 原树 点分树

前言

还真没有。

点分树

点分树是个神秘的东西。

点分树是通过更改原树形态使树的层数变为稳定 \(\log n\) 的一种重构树。

常用于解决与树原形态无关的带修改问题。

是这样的,有些树上问题,看起来用别的树形结构做不了,点分治能做。

但是它不仅多次询问(\(n\) 同级),还带修,有时候甚至强制在线,点分治直接去世了!

这个时候,当问题又跟树的形态关系不算很大时候(说完全无关也不可能是吧),点分树登场。

它提前做一遍点分治,建立一棵重构树,然后每次询问直接在这颗树上搞就行了。

树的建法

其实很简单,把点分治每轮的重心连起来就行了。

(比如说第一轮选中重心,分成很多个子树递归求解,每个子树的重心都向原重心连边,这样做下去就行了)

容易发现这样做完后,树的形态和原树大相径庭,但是它仍有 3 条重要性质:

  1. 树高 \(O(\log n)\) 级别,意味着很多暴力算法在这棵树上做复杂度也是 \(O(n\log n)\) 的。

  2. 两点点分树上的 lca 必然存在于两点原树路径上。

  3. 每个点的子树正式点分治算法时候它的子树。

简单运用

那么具体有什么用?我们看一道例题:

一棵树,单点修改,查询所有到某点距离小于等于某个值的点权值和,多次询问,强制在线。

先考虑不带修改,单次询问,此时可以使用暴力点分治,每次问一个子树内距离根(指重心)小于等于某个数的点求和,注意有不少的子树根本不用进去,在大的子树算完后只需要进入那个点所在的子树就行了,注意要把这个子树刚刚计算的答案(错误的答案)容斥掉。

好的带上多测之后,发现我在询问“子树内距离根(指重心)小于等于某个数的点求和”时候,子树变化很小,每次暴力算太愚蠢了。

那么我们能不能把这个结果预处理一下?可以的。

事实上,我们对于每个点都开一个值域线段树,上面存好子树内所有点到根的距离对应的和,然后询问的话直接查询一个前缀和就好了。

考虑容斥时候,减去的那个子树的计算是按到上一级重心的距离,但是到上一级重心又属于这个子树的已经在上面的线段树里打乱了,没法调出来单独求和。

咋办?每个点多开一个线段树存储上一级就行了。

修改的话,这个点到点分树的跟路径上都要修改,改动它们的两棵线段树。

注意:点分树的形态和原树大相径庭,故距离的变化并不是单调的,要经常性的求树上两点的距离,实现要求不精细直接倍增即可,要求精细就写个 dfs 序来 \(O(1)\) 查询(也存在直接存储的做法,不过我懒得搞了)。

以下是非常史的本题 AC 代码。

注释过几天加上。

#include<bits/stdc++.h>
using namespace std;
#define N 114514
int vis[N],a[N];
vector<int> e[N];
int sz[N],mxs[N],rt;
vector<int> col[N];
//LCA
int d[N],f[N][20];
void dfs(int u)
{
	for(int i=1;(1<<i)<=d[u];i++) f[u][i]=f[f[u][i-1]][i-1];
	for(auto v:e[u]) if(v!=f[u][0])
		d[v]=d[u]+1,f[v][0]=u,dfs(v);
}
int dis(int xx,int yy)
{
	if(d[xx]<d[yy]) swap(xx,yy);
	int x=xx,y=yy;
	for(int i=17;i>=0;i--) if(d[f[x][i]]>=d[y]) x=f[x][i];
	if(x==y) return d[xx]-d[x];
	for(int i=17;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	int lca=f[x][0];
	return d[xx]+d[yy]-2*d[lca];
}
//Sgt
const int maxx=99999;
int tot,root[N][2];
int ls[N*100],rs[N*100],val[N*100];
#define mid ((l+r)>>1)
void change(int &o,int l,int r,int x,int v)
{
	if(!o) o=++tot;
	val[o]+=v;
	if(l==r) return;
	if(x<=mid) change(ls[o],l,mid,x,v);
	else change(rs[o],mid+1,r,x,v);
}
int ask(int o,int l,int r,int al,int ar)
{
	if(ar<0) return 0;
	if(al<=l&&r<=ar) return val[o];
	int ans=0;
	if(ar>mid) ans+=ask(rs[o],mid+1,r,al,ar);
	if(al<=mid) ans+=ask(ls[o],l,mid,al,ar);
	return ans;
}

int d1[N];
void dfs1(int u,int fa,int n)
{
	sz[u]=1;mxs[u]=0;
	for(auto v:e[u]) if(!vis[v]&&v!=fa) dfs1(v,u,n),sz[u]+=sz[v],mxs[u]=max(mxs[u],sz[v]);
	mxs[u]=max(mxs[u],n-sz[u]);
	if(mxs[u]<=mxs[rt]) rt=u;
}
void dfs2(int u,int d,int fa)
{
	if(d1[u]!=-1) change(root[rt][1],0,maxx,d1[u],a[u]);
	change(root[rt][0],0,maxx,d,a[u]);
	d1[u]=d;
	for(auto v:e[u]) if(v!=fa&&!vis[v]) dfs2(v,d+1,u);
}

int dfroot,dffa[N];
void solve(int r,int n,int lastrt)
{
	rt=0;mxs[0]=998244353;
	dfs1(r,0,n);
	int rooooot=rt;
	dffa[rt]=lastrt;
	change(root[rt][0],0,maxx,0,a[rt]);
	if(d1[rt]!=-1) change(root[rt][1],0,maxx,d1[rt],a[rt]);
	for(auto v:e[rt]) if(!vis[v]) dfs2(v,1,rt);
	vis[rt]=1;
	for(auto v:e[rt]) if(!vis[v])
	{
		if(sz[v]<sz[rt]) solve(v,sz[v],rt);
		else solve(v,n-sz[rt],rooooot);
	}
	dfroot=rooooot;
}
int query(int x,int k)
{
	int ans=0,ok=k,xx=x;
	while(x!=dfroot)
	{
		ans+=ask(root[x][0],0,maxx,0,k)-ask(root[x][1],0,maxx,0,ok-dis(xx,dffa[x]));
		x=dffa[x];
		k=ok-dis(xx,x);
	}
	return ans+ask(root[x][0],0,maxx,0,k);
}
void modify(int x,int y)
{
	int zl=y-a[x];
	a[x]=y;
	int xx=x;
	while(x!=dfroot)
	{
		change(root[x][0],0,maxx,dis(xx,x),zl);
		change(root[x][1],0,maxx,dis(xx,dffa[x]),zl);
		x=dffa[x];
	}
	change(root[x][0],0,maxx,dis(xx,x),zl);
}
int n,q,lastans;
int main()
{
	memset(d1,-1,sizeof(d1));
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	int u,v;
	for(int i=1;i<n;i++) cin>>u>>v,e[u].push_back(v),e[v].push_back(u);
	dfs(1);
	solve(1,n,0);
	while(q--)
	{
		int op,x,y;
		cin>>op>>x>>y;x^=lastans,y^=lastans;
		if(op==0)
		{
			lastans=query(x,y);
			cout<<lastans<<endl;
		}
		else modify(x,y);
	}
	return 0;
}

标签:子树,重心,int,分治,笔记,学习,原树,点分树
From: https://www.cnblogs.com/FunStrawberry/p/18306031

相关文章

  • Java基础-学习笔记04
    04运算符进制1.运算符逻辑与&和短路与&&共同点:两个条件都为true时,结果为true,否则为false。&逻辑与:不管第一个条件是否为false,第二个条件都要判断;&&短路与:如果第一个条件判断为false,则第二个条件不会判断。逻辑或|和短路或||共同点:两个条件只要有一个成立,结果......
  • P27-P47构建神经网络进化智能体-构建用于训练强化学习之鞥提的随机环境-构建基于价值
    文章目录构建神经网络进化智能体前期准备实现步骤工作原理参考资料第二章基于价值、策略和行动者-评论家的深度强化学习算法实现技术要求构建用于训练强化学习智能体的随机环境前期准备实现步骤工作原理构建基于价值的强化学习智能体算法前期准备实现步骤工作原理......
  • 第八天笔记(项目测试工具悟道使用)
    禅道一、禅道的介绍(1)定义禅道是一个项目管理工具,也是一个bug管理工具,还是一个用例管理工具。(2)作用:为了解决众多企业在管理中出现混乱,无序的现象,开发出来(3)来源:禅道属易软天川公司(4)禅道是集于产品管理,项目管理,测试管理于一身,同时包含事务管理,组织管理8众多功能,是中小企业管理......
  • 逻辑回归(机器学习)
    上一题篇文章写了线性回归以及梯度下降法,这篇文章讲一下逻辑回归。虽然它叫逻辑回归,但是它并非回归模型,而是一个分类模型。那么回归和分类有什么区别呢?在上一篇文章中,我们以住房各特征预测了房价中位数。这个是给定数据,预测一个连续的数据。而分类呢?还是举出上面的例子,只不过这......
  • 7/16 训练笔记
    闲话插,就硬插,插完就过了(P4781【模板】拉格朗日插值模板题,写拉格朗日插值即可。代码:#include<bits/stdc++.h>#defineintlonglong#definerep(i,l,r)for(inti=l;i<=r;i++)usingnamespacestd;constintmod=998244353;intx[2010],y[2010],n,k;int......
  • 整型,浮点型,字符型数据类型学习
    整型数据整型常量表示:在C语言中,有三种形式:           十进制整数如:123、-123八进制整数以0开头的数,如0123表示八进制数123十六进制整数以0x开头的数,如0x123表示十六进制数123整型变量:    数据在内存中以二进制形式存放。    数值以......
  • 低开开发笔记(八): 低代码编辑器实现撤销回退(命令模式,防抖处理)
    好家伙, 0.代码已开源https://github.com/Fattiger4399/ph_questionnaire-.git 1.事件触发我们先从事件的触发开始讲起大致上我们有两个思路可以选择1.监控用户行为2.监控数据变化 两种选择都会有较难处理的部分,这里我们先选第二个选项 关于监控数据,首......
  • 机器学习中的梯度下降
            本文只是简单解释一下梯度下降,其中涉及到的公式并没有展示说明。1.什么是梯度?        梯度也可以理解为导数。        在一维空间中:梯度就是导数,或者说对于一个线性函数,也就是线的斜率。2.什么是梯度下降?    梯度是个向量,自变量......
  • 【新手硬件工程师学习日记】之电容的九大作用
    1、隔直流隔直流电容作用:阻止直流通过而让交流通过。2、旁路(去耦)高频旁路电容器作用:为交流电路中某些并联的组件提供低阻抗通路。3、耦合耦合电容电路模型作用:作为两个电路之间的连接,允许交流信号通过并传输到下一级电路。......
  • 【硬件学习日记】之丝印摆放
        在原理图部分结束之后就要转到PCB给元器件布局了,那么为了更好地给元器件布局还不妨碍视野,我一般选择把元器件的丝印缩小摆放在元器件中心点,效果如下:步骤如下:①Ctrl+A选择全部元器件②快捷键A+P,点“标识符”这一侧的中间这个点。如果你想把丝印放在元器件的别......