[學習筆記] 樹上差分 - 圖論

XiaoLe_MC發表於2024-04-13

前置知識:樹,LCA,字首和與差分

邊差分

這個名字是在網上看到的,不理解為什麼要叫這麼一個名字,可能是因為它與 樹鏈修改 有關。當然,用於 樹鏈修改 單點查詢 非常方便~

image

看這個圖,該圖是以點1為根進行DFS的。如果我們要把從3 -> 4這條樹鏈上所有的點統統加上1,可以都轉化為對到根節點的樹鏈的操作,我們可以把3 -> 1全加上1,4 -> 1全加上1,發現2多加了1,1多加了2,所以2 - > 1減掉1,1 -> 1減掉1。這並不難想。但是如何實現把某一結點到根節點上所有的點進行權值加減呢?

聯想到差分,我們可以分樹鏈差分,對於3 -> 1鏈上的點,將這條鏈的開始(節點3)+1,將這條鏈的末尾(節點0)-1,最後查詢時,把這個節點的子節點權值和自身權值相加即為最終權值。

image

畫圖太難用了!

看一道例題:[JLOI2014] 松鼠的新家

一道典型的樹鏈修改 單點查詢 + LCA題目。需要注意,我們在對每段路徑進行操作的時候,前一個路徑的尾端點與後一個路徑的首端點多放了一個糖果,而且,最後一段路徑的右端點是不用放糖果的,所以要統統減掉。

#include<bits/stdc++.h>
using namespace std;
#define min(x,y) (in[x]<in[y])?x:y
const int N = 3e5 + 1;
int n, ord[N], in[N], st[19][N], tot, w[N];
vector<int> G[N];
bitset<N> flag;
inline void dfs1(int k, int fa){
	st[0][in[k] = ++tot] = fa;
	for(int v : G[k]) if(!in[v]) dfs1(v, k);
}
inline void dfs2(int k){
	flag[k] = 1;
	for(int v : G[k]) if(!flag[v]) dfs2(v), w[k] += w[v];
}
inline int lca(int a, int b){
	if(a == b) return a;
	if((a = in[a]) > (b = in[b])) swap(a, b);
	int k = __lg(b-a++);
	return min(st[k][a], st[k][b-(1<<k)+1]);
}
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n;
	for(int i=1; i<=n; ++i) cin>>ord[i];
	for(int i=1, a, b; i<n; ++i){
		cin>>a>>b;
		G[a].push_back(b), G[b].push_back(a);
	}
	dfs1(1, 0);
	for(int i=1; i<=__lg(n); ++i)
	for(int j=1; j<=n-(1<<i)+1; ++j)
		st[i][j] = min(st[i-1][j], st[i-1][j+(1<<i-1)]);
	for(int i=1; i<n; ++i){
		int a = ord[i], b = ord[i+1];
		int fa = lca(a, b);
//		printf("lca(%d, %d) = %d\n", a, b, fa);
		++w[a], ++w[b], --w[fa], --w[st[0][in[fa]]];
	}	
	dfs2(1);
	for(int i=1; i<=n; ++i){
		if(i != ord[1]) --w[i];
		cout<<w[i]<<'\n';
	}
	return 0;
}

相關文章