【学习笔记】树上差分总结(点差分/边差分)

一.树上差分的基本概念

1.树上差分的定义

树上差分,顾名思义,意思就是在树上做差分

至于什么是差分呢?如果不会的同学,可以先看看我的这篇博客:一维,二维差分の详解(简单易懂)_一维差分-CSDN博客

2.树上差分能解决的问题

树上差分有什么作用?举个例子,如果题目要求对树上的一段路径进行操作,并询问某个点或某条边被经过的次数,树上差分就可以派上用场了。

类比于差分数组,树上差分利用的思想也是前缀和思想。(在这里应该是子树和思想

树上差分,就是利用差分的性质,对路径上的重要节点进行修改(而不是暴力全改),作为其差分数组的值,最后在求值时,利用dfs遍历求出差分数组的前缀和得出答案,就可以达到降低复杂度的目的。

树上差分时需要求LCA,不会的同学可以先看看我的这篇博客:详解最近公共祖先(LCA)-CSDN博客

树上差分一般有两种类型的题目,一种是对边进行差分,另一种就是对点进行差分

下面我将会分别讲解一下这两种问题。

二.点差分

1.思路

直接去dfs暴力加点权的话,肯定会TLE,但是我们现在在讲啥?树上差分啊!

假设需要将两点u,v之间路径上的所有点权增加x,l是lca(u,v),p是l的父亲节点,则差分操作如下:

sum[u] += x;
sum[v] += x;
sum[l] -= x;
sum[p] -= x;

举个栗子(其中假设x=1):

其中s和t就是题目中树上点权需要加1的节点的起始点,绿色的数字代表点权(已经加1了) 

则操作后有:

至于为什么要这么操作呢?别急,继续往下看。

做完上述的差分操作后,我们就要统计答案了。

当我们dfs搜索到s,向上回溯。

下面以u表示当前dfs搜索到的节点

对于每个u统计它的子树大小(要用前缀和的思想记录每个点的点权了),顺着路径标起来。

(即sum[u] += sum[son])

我们会发现第一次从s回溯到s与t的LCA时候,sum[LCA(s,t)] += sum[son[LCA(s,t)]]

此时sum[LCA(s,t)]=0(-1+1=0)。这时我们不禁会有一个疑问: "不是LCA(s,t)会被经过一次嘛,为什么是0!"

别急,我们继续搜另一边。.

继续:我们搜索到t,向上回溯。

依旧统计每个u的子树大小sum[u]+=sum[son]

再度回到LCA(s,t),依旧是sum[LCA(s,t)]+=sum[son[LCA(s,t)]]

这个时候 sum[LCA(s,t)]=1 这就达到了我们要的效果 (是不是特别优秀φ(゜▽゜*)♪)

但是我们还要思考一个问题:万一我们再从LCA(s,t)向上回溯的时候使得其父亲节点的子树和为1怎么办?这样我们不就使得其父亲节点被多经过了一次?

其实很简单,我们只需要在前面差分操作时将sum[fa[lca(s,t)]]-=x就行了。

这样就达到了标记我们路径上的点的要求! 是否有一种恍然大悟的感觉呢?

 

 2.例题Max flow

问题

参考代码

#include<bits/stdc++.h>
using namespace std;
int n,q,mx[300001][41],deep[300001],sum[300001],ans;
vector<int> vec[300001];
void dfs(int x,int fa)//lca的初始化
{deep[x] = deep[fa] + 1;mx[x][0] = fa;for(int i = 0;i < vec[x].size();i++)if(vec[x][i] != fa)dfs(vec[x][i],x);
}
int lca(int x,int y)//倍增法求lca
{if(deep[x] < deep[y]) swap(x,y);for(int i = 40;i >= 0;i--)if(deep[mx[x][i]] >= deep[y])x = mx[x][i];if(x == y) return x;for(int i = 40;i >= 0;i--)if(mx[x][i] != mx[y][i]){x = mx[x][i];y = mx[y][i];}return mx[x][0];
}
void dfss(int x,int fa)//统计答案的最大值
{for(int i = 0;i < vec[x].size();i++){int t = vec[x][i];if(t == fa) continue;dfss(t,x);sum[x] += sum[t];//在树上进行类似于前缀和的操作}ans = max(ans,sum[x]);//取最大值
}
signed main()
{cin>>n>>q;for(int i = 1;i < n;i++){int u,v;scanf("%d%d",&u,&v);vec[u].push_back(v);vec[v].push_back(u);}dfs(1,0);for(int j = 1;j <= 40;j++)for(int i = 1;i <= n;i++)mx[i][j] = mx[mx[i][j - 1]][j - 1];while(q--){int u,v;cin>>u>>v;int l = lca(u,v);sum[u]++;//树上差分sum[v]++;sum[l]--;if(l != 1) sum[mx[l][0]]--;//如果l有父节点就进行后面的操作}dfss(1,0);cout<<ans;return 0;
}

三.边差分

1.思路

思想其实和点差分一样的,我来讲一下操作。

将两点s,t之间路径上的所有边权增加xl=LCA(s,t)以每条边两端深度较大的节点存储该边的差分数组,则操作如下:

sum[s] += x;
sum[t] += x;
sum[l] -= 2 * x;

举个栗子(其中假设x=1):

红色边为需要经过的边,绿色的数字代表经过次数

但是由于我们不能储存边权,所以只能把边权塞给了点权,因此我们的图应该是这样的

 

这样的话我们只要把sum[s]++,sum[t]++,sum[lca(s,t)]-=2就可以实现差分操作了。

同样地,只要dfs一遍,遍历时统计以每个节点为根的树的节点的权值和,就是当前节点到父亲节点的边的最终权值了!

 是不是很厉害

至于为什么点差分和边差分的操作不一样,很简单,请读者自己思考。

树上差分主要还是学习思想吧!

2.例题树上必经边--边差分

问题

 参考代码

#include<bits/stdc++.h>
using namespace std;
int n,q,mx[300001][41],deep[300001],sum[300001],ans,k;
vector<int> vec[300001];
void dfs(int x,int fa)
{deep[x] = deep[fa] + 1;mx[x][0] = fa;for(int i = 0;i < vec[x].size();i++)if(vec[x][i] != fa)dfs(vec[x][i],x);
}
int lca(int x,int y)
{if(deep[x] < deep[y]) swap(x,y);for(int i = 40;i >= 0;i--)if(deep[mx[x][i]] >= deep[y])x = mx[x][i];if(x == y) return x;for(int i = 40;i >= 0;i--)if(mx[x][i] != mx[y][i]){x = mx[x][i];y = mx[y][i];}return mx[x][0];
}
void dfss(int x,int fa)
{for(int i = 0;i < vec[x].size();i++){int t = vec[x][i];if(t == fa) continue;dfss(t,x);sum[x] += sum[t];}
}
signed main()
{cin>>n>>q>>k;for(int i = 1;i < n;i++){int u,v;scanf("%d%d",&u,&v);vec[u].push_back(v);vec[v].push_back(u);}dfs(1,0);for(int j = 1;j <= 40;j++)for(int i = 1;i <= n;i++)mx[i][j] = mx[mx[i][j - 1]][j - 1];while(q--){int u,v;cin>>u>>v;int l = lca(u,v);sum[u]++;sum[v]++;sum[l] -= 2;}dfss(1,0);for(int i = 2;i <= n;i++)if(sum[i] == k)ans++;cout<<ans;return 0;
}

四.BB in last 

如果这篇博客对您有帮助的话,别忘了点赞收藏加关注支持一下吖~(小声BB)(o゚v゚)ノ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/456494.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java EE】----Bean的作用域和生命周期

1.Bean的作用域 定义&#xff1a;Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式&#xff0c;⽐如 singleton 单例作⽤域&#xff0c;就 表示 Bean 在整个 Spring 中只有⼀份 &#xff08;产生的原因&#xff0c;Bean 默认情况下是单例状态&#xff08;singleton&…

c#cad 创建-多线段(三)

运行环境 vs2022 c# cad2016 调试成功 一、程序说明 AutoCAD中创建多段线的。具体解释如下&#xff1a; 获取当前文档和数据库&#xff0c;并创建一个编辑器&#xff08;用于与用户交互&#xff09;。使用事务处理的方式&#xff0c;开始对数据库的操作。打开模型空间&…

mysql事务锁

Lock - 事务锁 与 latch 的区别 lock对象是事务&#xff0c;用来锁定的是数据库中的对象&#xff0c;如表、行、页。并且一般lock的对象仅在事务commit或rollback后进行释放&#xff08;不同事务隔离级别释放的时间可能不同&#xff09;。此外&#xff0c;lock&#xff0c;正…

Damn Small Linux 停更16年后,2024 回归更新

Damn Small Linux(DSL) 发行版释出了最新的 2024 版本&#xff0c;并重新定义了什么叫“Damn Small”。 DSL 诞生于 2005 年&#xff0c;原本是尝试提供一个 50MB 大小的 LiveCD&#xff0c;2008 年开发停滞。 2024 年原作者 John Andrews 宣布 DSL 复活&#xff0c;在几乎所…

信号系统之线性系统详解

1 线性系统 信号描述了一个参数如何随另一个参数变化。例如&#xff0c;电子电路中的电压随时间变化&#xff0c;或图像中随距离变化的亮度。系统是响应输入信号而产生输出信号的任何过程。如图中的框图所示。 有几个规则用于命名信号&#xff1a; 连续信号使用圆括号&#x…

Android:Android Studio安装及环境配置

1开发环境搭建 Android开发需要使用java的jdk环境,所以需要下载JAVA JDK。 1.1安装配置JAVA JDK Java的JDK下载: https://www.oracle.com/technetwork/java/javase/downloads/index.html 配置java的环境变量: JAVA_HOME:java安装路径。 新增环境变量CLASSPATH 在Path环境…

执行时长 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 为了充分发挥GPU算力&#xff0c;需要尽可能多的将任务交给GPU执行&#xff0c;现在有一个任务数组&#xff0c;数组元素表示在这1秒内新增的任务个数且每秒都有新…

浅谈交换原理(3)——交换网络

一、基本概念 交换网络是由若干个交换单元按照一定的拓扑结构和控制方式构成的网络。交换网络的三个基本要素是&#xff1a;交换单元、不同交换单元间的拓扑连接和控制方式。 1.1 单机交换网络与多级交换网络 交换网络按拓扑连接方式可分为&#xff1a;单级交换网络和多级交换网…

2024年对于Windows电脑,有类似的清理软件推荐吗?

对于Windows电脑&#xff0c;有很多类似的清理软件可以选择。以下是一些推荐的清理软件&#xff1a; CCleaner&#xff1a;这是一款非常受欢迎的Windows清理软件&#xff0c;它可以清理系统垃圾文件、浏览器缓存、无效注册表项等&#xff0c;还可以优化系统性能。CCleaner具有简…

【Qt学习笔记】(三)常用控件(持续更新)

Qt 常用控件 1 控件概述2 QWidget 控件核心属性2.1 enabled2.2 geometry2.3 window frame 的影响2.4 windowTitle2.5 window Icon2.6 windowOpacity2.7 cursor2.8 font2.9 toolTip2.10 focusPolicy2.11 stylesheet 1 控件概述 Widget是Qt中的核心概念英文原义是"小部件&q…

并发容器【ConcurentHashMap、CopyOnWriteArrayList、阻塞队列、ArrayBlockingQueue】

并发容器 什么是并发容器?同步容器:并发容器: ConcurrentHashMap结构图JDK1.7结构图JDK1.8结构图 CopyOnWriteArrayList实现原理 并发队列阻塞队列ArrayBlockingQueue 转自极客时间 什么是并发容器? 在JUC包中&#xff0c;有一大部分是关于并发容器的&#xff0c;如Concurr…

go语言每日一练——链表篇(六)

传送门 牛客面试必刷101题—— 判断链表中是否有环 牛客面试必刷101题—— 链表中环的入口结点 题目及解析 题目一 代码 package mainimport . "nc_tools"/** type ListNode struct{* Val int* Next *ListNode* }*//**** param head ListNode类* return bool…