树上差分的公式推导

今天写了一道题目,需要采用线段树合并+树上差分来解决

题目链接:P1600 [NOIP2016 提高组] 天天爱跑步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

其实当时已经想到要用这两种方法,但苦于一直找不到转移方程,最后看了答案才领悟到一点推导公式的思路

我一开始的想法是对于起点s和终点t而言,对于x到其lca的路径中的节点,有人出现的时间结点就是dep[x]一直往上递推过去,然后我只需要最后合并找出符合w[i]的时间点就行了

但细想一下这个方法根本就不能实现,首先是lca到t的路径的结点无法处理,其次是差分数组加和的时候每次区间下标都要+1,而且也不知道x出发的人到哪个结点就消失,所以这种顺推的递推公式并不成立

那正确的思路是什么呢?

对于x到y路径

我们先分析x到lca的路径上的结点i,对于这个节点i,如果x出发的人能够被他观测到,那么应该满足关系式:dep[x]-dep[i]=w[i],这样一来,我们就可以得到:dep[i]+w[i]=dep[x],也就是说,对于路径上的每个这样的结点i,我们只要计数dep[x]的个数,就可以知道他能够观测到多少个人了

我们再来分析lca到y的路径上的结点j,对于这个结点j,如果x出发的人能够被他观测到,那么应该满足关系式:dep[x]-dep[lca]+dep[j]-dep[lca]=w[j],这样一来,我们就可以得到:dep[j]-w[i]=2*dep[lca]-dep[x],也就是说,对于路径上的每个这样的结点j,我们只需要计数2*dep[lca]-dep[x]的个数,就可以知道他可以观测到多少个人了

那么我们需要从哪里开始修改呢?

显然这些dep[x],2*dep[lca]-dep[x]所代表的人的个数只在x到y这条路径上生效,也就是说,我们只需要修改x结点上dep[x]的个数,然后在由下往上合并差分数组的时候,每个在路径(x,lca)的结点都可以享受到这份dep[x]的贡献。同理,我们只需要修改y结点上2*dep[lca]-dep[x]的个数,那么每个在路径(lca,y)的节点都可以享受到这份2*dep[lca]-dep[x]的贡献

而对于他们的lca来说,它既享受到了dep[x]的贡献,又享受到了2*dep[lca]-dep[x]的贡献,哪份是它不应该拥有的呢?其实这两者在这个节点这里是等效的,所以我们随机减免一个就可以了,我们这里选择给dep[x]的权值减1。那么对于lca的父亲而言,它就多享受到了一个2*dep[lca]-dep[x]的贡献,因此我们给其2*dep[lca]-dep[x]的权值-1,从而把这个差分修改的影响限制在了路径(x,y)中

这里我们选择用线段树来维护这个权值区间

实现代码如下:

数组版本:

//树上差分来处理树上路径的信息
//每个点建一棵权值线段树
//结点深度为区间下标
//sum维护结点深度出现的次数#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 3E5 + 10, M = N << 1;
#define mid (l+r>>1)
//链式前向星
int to[M], nxt[M], h[N], idx;
//左儿子,右儿子,sum差分权值和
int ls[N * 100], rs[N * 100], sum[N * 100];
//fa倍增父亲
//dep节点深度
int fa[N][22], dep[N];
//n棵线段树
int root[N], tot;
int lg[N];
//w:观察员
//ans:第i个节点的答案
int n, m, w[N], ans[N];
//加边
void add(int a,int b){to[++idx] = b;nxt[idx] = h[a];h[a] = idx;
}
//lg[i]==log2+1,i向下取整
void init(){for (int i = 1; i <= n;i++){lg[i] = lg[i - 1] + (1<<lg[i-1] == i);}
}
//快读
void read(int &x){x = 0;char c = getchar();while(!isdigit(c)){c = getchar();}while(isdigit(c)){x = (x << 3 + x << 1) + c - '0';c = getchar();}
}//树增
void dfs(int x,int f){dep[x] = dep[f] + 1;fa[x][0] = f;//距离x的递增父亲的距离不会超过x的深度for (int i = 1; i < lg[dep[x]];i++){//距离x的距离为2^i的父亲等于距离x的距离为2^(i-1)的父亲的距离它为距离2^(i-1)的父亲fa[x][i] = fa[fa[x][i - 1]][i - 1];}//递归每一个子结点for (int i = h[x], y; y = to[i];i=nxt[i]){if(y!=f)dfs(y, x);}
}int lca(int x,int y){//默认x为深度大的结点if(dep[x]<dep[y]){swap(x, y);}//往上爬的距离不会超过x与y的深度差for (int i = lg[dep[x] - dep[y]] - 1; ~i;i--){//如果跳到父亲的深度还是比y大,可以放心跳if(dep[fa[x][i]]>=dep[y]){x = fa[x][i];}}if(x==y){return y;}for (int i = lg[dep[x]] - 1; ~i;i--){if(fa[x][i]!=fa[y][i]){x = fa[x][i];y = fa[y][i];}}return fa[x][0];
}//动态开点
//单点修改
void change(int &u,int l,int r,int p,int k){if(!u)u = ++tot;if(l==r){sum[u] += k;return;}if(p<=mid){change(ls[u], l, mid, p, k);}else{change(rs[u], mid + 1, r, p, k);}
}//把x线段树合并到y上
int merge(int x,int y,int l,int r){if(!x||!y){return x + y;}if(l==r){sum[x] += sum[y];return x;}ls[x] = merge(ls[x], ls[y], l, mid);rs[x] = merge(rs[x], rs[y], mid + 1, r);return x;
}//单点查询
int query(int u,int l,int r,int p){if(l==r){return sum[u];}if(p<=mid){return query(ls[u], l, mid, p);}else{return query(rs[u], mid + 1, r, p);}
}//递归合并线段树
void dfs2(int x){for (int i = h[x], y; y = to[i];i=nxt[i]){if(y==fa[x][0])continue;dfs2(y);//由于整体向右平移了值域n,所以线段树的区间要开到2*nroot[x]=merge(root[x],root[y],1,n<<1);}//如果这里有观察员//而且if(w[x]&&n+dep[x]+w[x]<=(n<<1)){ans[x] += query(root[x], 1, n << 1, n + dep[x] + w[x]);}ans[x] += query(root[x], 1, n << 1, n + dep[x] - w[x]);
}int main(){scanf("%d%d", &n, &m);for (int i = 1, x, y; i < n;i++){scanf("%d%d", &x, &y);add(x, y);add(y, x);}init();for (int i = 1; i <= n;i++){scanf("%d", w + i);}dfs(1, 0);for (int i = 1, x, y,l; i <= m;i++){scanf("%d%d", &x, &y);l = lca(x, y);change(root[x], 1, n << 1, n + dep[x], 1);change(root[y], 1, n << 1, n + 2 * dep[l] - dep[x], 1);change(root[l], 1, n << 1, n + dep[x], -1);change(root[fa[l][0]], 1, n << 1, n + 2 * dep[l] - dep[x],-1);}dfs2(1);for (int i = 1; i <= n;i++){printf("%d ",ans[i]);}return 0;
}

指针版本:

#include <iostream>
#include <cstdio>
#include <cstring>
#define mid (l+r>>1)
using namespace std;
struct tree {struct tree* l = nullptr, * r = nullptr;int sum=0;
};
const int N = 3E5 + 10, M = N << 1;
int to[M], nxt[M], h[N], tot;
void add(int a, int b) {to[++tot] = b;nxt[tot] = h[a];h[a] = tot;
}
tree* tr[N];
int son[N], siz[N], top[N], dep[N], fa[N];
int ans[N], w[N];
int n, m;
//fa,dep,siz,son
void dfs1(int u) {dep[u] = dep[fa[u]] + 1;siz[u] = 1;for (int i = h[u], v; v = to[i]; i = nxt[i]) {if (v == fa[u])continue;fa[v] = u;dfs1(v);siz[u] += siz[v];if (siz[v] > siz[son[u]])son[u] = v;}
}//top
void dfs2(int u, int tp) {top[u] = tp;top[son[u]] = tp;if(son[u])dfs2(son[u], tp);for (int i = h[u], v; v = to[i]; i = nxt[i]) {if (v == fa[u] || v == son[u])continue;dfs2(v, v);}
}//公共祖先
int lca(int x, int y) {//不在一条链上的时候while (top[x] != top[y]) {if (dep[top[x]] < dep[top[y]]) {swap(x, y);}x = fa[top[x]];}return dep[x] < dep[y] ? x : y;
}//点修
void change(tree** u, int l, int r, int p, int k) {if (!*u) {*u = new tree;}if (l == r) {(*u)->sum += k;return;}if (p <= mid) {change(&((*u)->l), l, mid, p, k);}else {change(&((*u)->r), mid + 1, r, p, k);}//反正不用区间查找,所以不Pushup也没关系了
}//点查
int query(tree* u, int l, int r, int p) {if (!u) {return 0;}if (l == r) {return u->sum;}if (p <= mid) {return query(u->l, l, mid, p);}else {return query(u->r, mid + 1, r, p);}
}tree* merge(tree* x, tree* y) {if (!x || !y) {return x ? x : y;}x->sum += y->sum;x->l = merge(x->l, y->l);x->r = merge(x->r, y->r);return x;
}void dfs3(int u) {for (int i = h[u], v; v = to[i]; i = nxt[i]) {if (v == fa[u])continue;dfs3(v);tr[u] = merge(tr[u], tr[v]);}if (w[u]&&dep[u] + w[u] <= n) {ans[u] += query(tr[u], -n, n, dep[u] + w[u]);}ans[u] += query(tr[u], -n, n, dep[u] - w[u]);
}int main() {scanf("%d%d", &n, &m);for (int i = 1, x, y; i < n; i++) {scanf("%d%d", &x, &y);add(x, y);add(y, x);}for (int i = 1; i <= n; i++) {scanf("%d", w + i);}dfs1(1);dfs2(1, 1);for (int i = 1, s, t, l; i <= m; i++) {scanf("%d%d", &s, &t);l = lca(s, t);change(&tr[s], -n, n, dep[s], 1);change(&tr[t], -n, n, 2 * dep[l] - dep[s], 1);change(&tr[l], -n, n, dep[s], -1);change(&tr[fa[l]], -n, n, 2 * dep[l] - dep[s], -1);}dfs3(1);for (int i = 1; i <= n; i++) {printf("%d ", ans[i]);}return 0;
}

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

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

相关文章

java中可变参数

在Java中&#xff0c;... 是可变参数&#xff08;varargs&#xff09;的语法&#xff0c;用于允许一个方法接受可变数量的参数。可变参数的引入使得方法调用更加灵活和简洁。以下是对可变参数的详细解释和使用示例。 可变参数的定义和使用 定义&#xff1a; 在方法参数列表中…

22-Pandas日期时间格式化

Pandas日期时间格式化 当进行数据分析时&#xff0c;我们会遇到很多带有日期、时间格式的数据集&#xff0c;在处理这些数据集时&#xff0c;可能会遇到日期格式不统一的问题&#xff0c;此时就需要对日期时间做统一的格式化处理。比如“Wednesday, June 6, 2020”可以写成“6…

Rust: polars行遍历,从dataframe到struct及Bar设计比较

pandas提供了iterrows()、itertuples()、apply等行遍历的方式&#xff0c;还是比较方便的。 polars的列操作功能非常强大&#xff0c;这个在其官网上有详细的介绍。由于polars底层的arrow是列存储模式&#xff0c;行操作效率低下&#xff0c;官方也不推荐以行方式进行数据操作。…

react_后台管理_项目

目录 1.运行项目 2. 项目结构 ①项目顶部导航栏 ②项目左侧导航栏 ③主页面-路由切换区 本项目使用的是 reacttsscss 技术栈。 1.运行项目 在当前页面顶部下载本项目&#xff0c;解压后使用编辑器打开&#xff0c;然后再终端输入命令&#xff1a; npm i 下载依赖后&am…

【应急响应】Windows应急响应 - 基础命令篇

前言 在如今的数字化时代&#xff0c;Windows系统面对着越来越复杂的网络威胁和安全挑战。本文将深入探讨在Windows环境下的实战应急响应策略。我们将重点关注实际应急响应流程、关键工具的应用&#xff0c;以及如何快速准确地识别和应对安全事件。通过分享实际案例分析&#…

FIO压测磁盘性能以及需要注意的问题

一、压测类型 1、顺序读&#xff08;IO&#xff09;&#xff1a;read&#xff0c;bs1M&#xff0c;job数从1开始往上加&#xff1a;2、3、4... 2、顺序写&#xff08;IO&#xff09;&#xff1a;write&#xff0c;bs1M&#xff0c;job数从1开始往上加&#xff1a;2、3、4... …

如何通过 1688 商品详情的 API 接口获取商品的详细信息

在当今数字化商业的大背景下&#xff0c;能够从 1688 这样规模庞大且商品种类丰富的电商平台中准确、高效地获取商品的详细信息&#xff0c;对于众多企业和开发者而言&#xff0c;具有举足轻重的意义。而通过 1688 商品详情的 API 接口来实现这一目标&#xff0c;无疑是一种强大…

【ACM出版,马来西亚-吉隆坡举行】第四届互联网技术与教育信息化国际会议 (ITEI 2024)

作为全球科技创新大趋势的引领者&#xff0c;中国不断营造更加开放的科技创新环境&#xff0c;不断提升学术合作的深度和广度&#xff0c;构建惠及各方的创新共同体。这是对全球化的新贡献&#xff0c;是构建人类命运共同体的新贡献。 第四届互联网技术与教育信息化国际学术会议…

【 木兰宽松许可证】

木兰宽松许可证&#xff0c; 第1版 2019年8月 http://license.coscl.org.cn/MulanPSL 您对“软件”的复制、使用、修改及分发受木兰宽松许可证&#xff0c;第1版&#xff08;“本许可证”&#xff09;的如下条款的约束&#xff1a; 定义 “软件”是指由“贡献”构成的许可在“本…

【C++知识点总结全系列 (07)】:模板与泛型编程详细总结与分析

模板与泛型编程 1、概述(1)What&#xff08;什么是模板、泛型编程&#xff09;(2)Why(3)Which(4)模板参数A.WhatB.HowC.模板参数的类型成员D.默认模板参数 2、模板函数3、模板类(1)How&#xff08;如何定义和使用模板类&#xff09;(2)成员模板 4、模板实参推断(1)What&#xf…

入侵检测模型

入侵检测模型&#xff08;Intrusion Detection Model&#xff09;在网络安全中起着至关重要的作用。它们用于识别和响应未经授权的访问和攻击行为。以下是常见的入侵检测模型的详细介绍&#xff1a; 一、入侵检测模型分类 基于签名的入侵检测模型&#xff08;Signature-Based …

昇思25天学习打卡营第7天|Pix2Pix实现图像转换

文章目录 昇思MindSpore应用实践基于MindSpore的Pix2Pix图像转换1、Pix2Pix 概述2、U-Net架构定义UNet Skip Connection Block 2、生成器部分3、基于PatchGAN的判别器4、Pix2Pix的生成器和判别器初始化5、模型训练6、模型推理 Reference 昇思MindSpore应用实践 本系列文章主要…

大数据面试题之Flink(3)

如何确定Flink任务的合理并行度? Flink任务如何实现端到端一致? Flink如何处理背(反)压? Flink解决数据延迟的问题 Flink消费kafka分区的数据时flink件务并行度之间的关系 使用flink-client消费kafka数据还是使用flink-connector消费 如何动态修改Flink的配置&a…

实战:基于Java的大数据处理与分析平台

实战&#xff1a;基于Java的大数据处理与分析平台 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨如何利用Java构建高效的大数据处理与分析平台。…

Python基础003

Python流程控制基础 1.条件语句 内置函数input a input("请输入一段内容&#xff1a;") print(a) print(type(a))代码执行的时候遇到input函数&#xff0c;就会等键盘输入结果&#xff0c;已回车为结束标志&#xff0c;也就时说输入回车后代码才会执行 2.顺序执行…

pandas数据分析(5)

pandas使用Numpy的np.nan代表缺失数据&#xff0c;显示为NaN。NaN是浮点数标准中地Not-a-Number。对于时间戳&#xff0c;则使用pd.NaT&#xff0c;而文本使用的是None。 首先构造一组数据&#xff1a; 使用None或者np.nan来表示缺失的值&#xff1a; 清理DataFrame时&#xf…

深度学习之交叉验证

交叉验证&#xff08;Cross-Validation&#xff09;是一种用于评估和验证机器学习模型性能的技术&#xff0c;尤其是在数据量有限的情况下。它通过将数据集分成多个子集&#xff0c;反复训练和测试模型&#xff0c;以更稳定和可靠地估计模型的泛化能力。常见的交叉验证方法有以…

java设计模式(四)——抽象工厂模式

一、模式介绍 改善在工厂方法模式中&#xff0c;扩展时新增产品类、工厂类&#xff0c;导致项目中类巨多的场面&#xff0c;减少系统的维护成本&#xff0c;且一个工厂可以生成多种产品&#xff0c;而不是同一种的产品&#xff0c;比如一个工厂既可以生产鞋子又可以衣服&#…

解决数据库PGSQL,在Mybatis中创建临时表报错TODO IDENTIFIER,连接池用的Druid。更换最新版本Druid仍然报错解决

Druid版本1.1.9报错Caused by: java.sql.SQLException: sql injection violation, syntax error: TODO IDENTIFIER : CREATE TEMPORARY TABLE temp_ball_classify (id int8 NOT NULL,create_time TIMESTAMP,create_by VARCHAR,classify_name VARCHAR) 代码如下&#xff1a; 测…

四川蔚澜时代电子商务有限公司打造抖音电商服务新高地

在数字化浪潮汹涌澎湃的今天&#xff0c;电商行业以其独特的魅力和强大的市场潜力&#xff0c;成为了推动经济增长的新引擎。四川蔚澜时代电子商务有限公司&#xff0c;作为这个领域的佼佼者&#xff0c;正以其专业的服务、创新的理念和卓越的实力&#xff0c;引领抖音电商服务…