樹上差分的公式推導

今天寫了一道題目,需要采用線段樹合并+樹上差分來解決

題目鏈接: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.pswp.cn/diannao/39585.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39585.shtml
英文地址,請注明出處:http://en.pswp.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;引領抖音電商服務…