博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
树上差分
阅读量:3951 次
发布时间:2019-05-24

本文共 3377 字,大约阅读时间需要 11 分钟。

差分

差分是前缀和的逆运算。 d 1... n d_{1...n} d1...n是差分数组, a 1... n a_{1...n} a1...n是原数组。关系如下式。

d i = a i − a i − 1 d_i=a_i-a_{i-1} di=aiai1
则通过 d 1... n d_{1...n} d1...n数组求 a i a_i ai,为求 d 1... n d_{1...n} d1...n数组的前缀和。
a i = ∑ j = 1 i d j a_i = \sum_{j=1}^id_j ai=j=1idj
通过 d 1... n d_{1...n} d1...n数组求前缀和为
s u m i = ∑ i = 1 n ∑ j = 1 i d i = ∑ i = 1 n ( n − i + 1 ) d i sum_i=\sum_{i=1}^n{\sum_{j=1}^id_i}=\sum_{i=1}^n(n-i+1)d_i sumi=i=1nj=1idi=i=1n(ni+1)di

应用

求第一个不能减的区间。
二分借教室操作数,用差分数组 O ( n ) O(n) O(n)借教室,总时间复杂度为 O ( n log ⁡ m ) O(n\log m) O(nlogm)

#include 
#define debug(__x) cout<<#__x<<"="<<__x<
range;ll a[maxn]; // 差分数组int n,m;void sub(int l,int r){
// 借教室操作 for(int i=l;i<=r;++i){
a[range[i].s]-=range[i].d; a[range[i].t+1]+=range[i].d; }}void add(int l,int r){
// 撤销借教室操作 for(int i=l;i<=r;++i){
a[range[i].s]+=range[i].d; a[range[i].t+1]-=range[i].d; }}bool judge(){
// 判断是否有一天教室数不够借 int now = 0; for(int i=1;i<=n;++i){
now += a[i]; if(now<0)return false; } return true;}int main(){
cin.tie(0),cout.tie(0); ios::sync_with_stdio(false); cin>>n>>m; for(int now,last=0,i=1;i<=n;++i){
cin>>now; a[i] = now - last; last = now; } for(int d,s,t,i=0;i
>d>>s>>t; range.emplace_back(d,s,t); } sub(0,m-1); if(judge()){
cout<<0<
>1; sub(l,mid); // debug(l);debug(r); if(judge()){
l = mid + 1; }else{
r = mid; add(l,mid); } } cout<
<

树上差分

就是在树上进行差分操作,实现功能与差分一样,可以 O ( 1 ) O(1) O(1)实现区间修改。分为点差分和边差分。

点差分
cnt[u]+=x可理解为u到树跟的路径上所有节点的权重加x。在把(u,v)路径上的点权加x的操作如下。

cnt[u] += x;cnt[v] += x;cnt[lca] -= x;cnt[fa[lca]] -= x;

用差分数组求点权

v a l [ u ] = ∑ v ∈ S u b T r e e r o o t = u c n t [ v ] val[u]=\sum_{v\in SubTree_{root=u}}cnt[v] val[u]=vSubTreeroot=ucnt[v]
边差分
把每个点连到它的父节点的边权放在该点上。原理和点分治一样。在把(u,v)路径上的边权加x的操作如下。

cnt[u] += x;cnt[v] += x;cnt[lca] -= 2*x;

模板

在一棵树上,在给定点对的路径上的点权加1。求最大点权。树上差分最适合修改多查询少的题。这题只用1次查询。

#include 
#define debug(__x) cout<<#__x<<"="<<__x<
G[maxn];int f[maxn][15];int cnt[maxn];int dep[maxn];int ans=0;void dfs(int u,int fa){
f[u][0] = fa; dep[u] = dep[fa] + 1; for(int i=1;i<15;++i) f[u][i] = f[f[u][i-1]][i-1]; for(int son : G[u]){
if(son == fa)continue; dfs(son,u); }}int lca(int x,int y){
if(dep[x] < dep[y]) swap(x,y); int t = dep[x] - dep[y]; for(int i=0;i<15;++i) if(t&(1<
=0;--i){
if(f[x][i]!=f[y][i]){
x=f[x][i]; y=f[y][i]; } } return f[x][0];}void sum(int u,int fa){
for(int son : G[u]){
if(son == fa)continue; sum(son,u); cnt[u] += cnt[son]; }}int main(){
cin.tie(0),cout.tie(0); ios::sync_with_stdio(false); int n,k; cin>>n>>k; for(int u,v,i=1;i
>u>>v; G[u].push_back(v); G[v].push_back(u); } dfs(1,0); while(k--){
int u,v; cin>>u>>v; int ac = lca(u,v); ++cnt[u]; ++cnt[v]; --cnt[ac]; --cnt[f[ac][0]]; } sum(1,0); // 把差分数组转换为每个点的点权 for(int i=1;i<=n;++i){
if(cnt[i]>ans) ans = cnt[i]; } cout<
<

转载地址:http://ogkzi.baihongyu.com/

你可能感兴趣的文章
设计模式——设计模式三大分类以及六大原则
查看>>
Android开发——ListView局部刷新的实现
查看>>
Android开发——ListView的复用机制源码解析
查看>>
Android开发——架构组件LiveData源码解析
查看>>
IDEA常用快捷键整理
查看>>
【Vue】两个元素同一行显示
查看>>
XXL-Job使用
查看>>
如何在SwaggerAPI中添加统一授权认证
查看>>
多线程
查看>>
【Linux】Centos7 常用命令
查看>>
【Redis】Centos7下安装Redis
查看>>
【Redis】Centos7下搭建Redis集群
查看>>
【Redis】Centos7下搭建Redis集群——哨兵模式
查看>>
【Linux】本地ping不同VM虚拟机
查看>>
【SpringCloud】Hystrix
查看>>
快速阅读——《认知篇》
查看>>
【Asp.net】基本概念
查看>>
【Asp.net】Web服务器控件
查看>>
【Asp.net】内置对象
查看>>
C语言数据类型笔记 by STP
查看>>