树上差分
大概的介绍
树上的差分和普通的区间差分其实差不多,都是解决离线多次修改最后一次查询的问题的(不要告诉我你不会数组差分qwq)。
前置芝士:倍增求 L C A LCA LCA、正常的差分
点的差分
比如这样一个问题,每次修改对于树上的一条路径,这条路径上的所有点的点权加 k k k,最后查询求某个点的点权。
考虑模仿普通数组的差分做法,对于树上的节点维护差分数组 c c c,我们考虑将一条路径(起点和终点分别为 s , t s, t s,t)上的所有点的权值加 k k k,那么差分数组应该这样变化:
c [ s ] + = k , c [ t ] + = k c [ l c a ( s , t ) ] − = k , c [ f [ l c a ( s , t ) ] [ 0 ] ] − = k \begin{aligned} c[s] += k, \; &c[t] += k \\ c[lca(s, t)] -= k, \; c[&f[lca(s, t)][0]] -= k \end{aligned} c[s]+=k,c[lca(s,t)]−=k,c[c[t]+=kf[lca(s,t)][0]]−=k
上面的 l c a ( s , t ) lca(s, t) lca(s,t) 表示 s s s 和 t t t 的最近公共祖先, f f f 数组就是在倍增求 l c a lca lca 里用到的 f f f, f [ l c a ( s , t ) ] [ 0 ] f[lca(s, t)][0] f[lca(s,t)][0] 就表示最近公共祖先的爸爸。
这个操作应该不难理解,只要把式子列出来应该就 能看懂了。
在最后询问的时候,我们只需要做一个子树和就能得到每个点的权值了(就是 d f s dfs dfs 一遍就好了)。
找个题练练手:luogu3258 松鼠的新家
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 300300
#define MAXM 4 * MAXN
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <=c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int n = 0;
int a[MAXN] = { 0 };
int tot = 0;
int first[MAXN] = { 0 };
int nxt[MAXM] = { 0 };
int to[MAXM] = { 0 };
int c[MAXN] = { 0 };
int ans[MAXN] = { 0 };
inline void add(int x, int y){
nxt[++tot] = first[x];
first[x] = tot; to[tot] = y;
}
int dep[MAXN] = { 0 };
int f[MAXN][35] = { 0 };
void prework(int x, int fa){
dep[x] = dep[fa] + 1;
for(int i = 0; i <= 30; i++) f[x][i + 1] = f[f[x][i]][i];
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
f[y][0] = x;
prework(y, x);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i = 30; i >= 0; i--){
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
}
for(int i = 30; i >= 0; i--)
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
void dfs(int x, int fa){
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
dfs(y, x);
c[x] += c[y];
}
}
int main(){
n = in;
for(int i = 1; i <= n; i++) a[i] = in;
for(int i = 1; i < n; i++){
int x = in, y = in;
add(x, y); add(y, x);
}
prework(1, 0);
for(int i = 1; i < n; i++){
int x = a[i], y = a[i + 1];
int lca = LCA(x, y);
c[x]++, c[y]++, c[lca]--, c[f[lca][0]]--;
}
dfs(1, 0); c[a[1]]++;
for(int i = 1; i <= n; i++) cout << c[i] - 1 << '\n';
return 0;
}
边的差分
考虑这样一个问题,每次修改给一条路径,这条路径上的所有边边权加 1 1 1。最后查询边权。
记录两个数组 s u m sum sum 和 p r e v prev prev。 s u m x sum_x sumx 表示点 x x x 到 f a ( x ) fa(x) fa(x) 的这条边的出现次数。 p r e v prev prev 记录每个点到它的爸爸的那条边。
对于每一次操作给一个路径(起点和终点分别为 s , t s, t s,t),我们就这样处理:
s u m [ s ] + + , s u m [ t ] + + s u m [ l c a ( s , t ) ] − = 2 \begin{aligned} sum[s&]++, \; sum[t]++ \\ sum&[lca(s, t)]-=2 \end{aligned} sum[ssum]++,sum[t]++[lca(s,t)]−=2
然后我们就做完了。
再找个题练练手:NOIP2015 运输计划
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 300300
#define MAXM 4 * MAXN
#define INFI (int)3e8
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c= getchar();
}
return x;
}
int tot = 0;
int first[MAXN] = { 0 };
int nxt[MAXM] = { 0 };
int to[MAXM] = { 0 };
int value[MAXM] = { 0 };
inline void add(int x, int y, int weight){
nxt[++tot] = first[x];
first[x] = tot; to[tot] = y;
value[tot] = weight;
}
int pre[MAXN] = { 0 };
int dis[MAXN] = { 0 };
int dep[MAXN] = { 0 };
int f[MAXN][32] = { 0 };
void prework(int x, int fa){
dep[x] = dep[fa] + 1;
for(int i = 0; i <= 30; i++) f[x][i + 1] = f[f[x][i]][i];
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
f[y][0] = x; dis[y] = dis[x] + value[e]; pre[y] = value[e];
prework(y, x);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
for(int i = 30; i >= 0; i--){
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
}
for(int i = 30; i >= 0; i--)
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int n = 0; int m = 0;
struct Tedge{
int len;
int x, y, lca;
bool operator < (const Tedge &rhs) const { return len > rhs.len; }
}edge[MAXM];
int mx = 0; int cnt = 0;
int flag = 0;
int sum[MAXN] = { 0 };
int judge(int x, int fa, int cnt, int mx){
int num = sum[x];
for(int e = first[x]; e; e = nxt[e]){
int y = to[e];
if(y == fa) continue;
num += judge(y, x, cnt, mx);
}
if(num >= cnt and pre[x] >= mx) flag = 1;
return num;
}
int check(int x){
cnt = mx = flag = 0; memset(sum, 0, sizeof(sum));
for(int i = 1; i <= m; i++){
if(edge[i].len > x){
sum[edge[i].x]++, sum[edge[i].y]++, sum[edge[i].lca] -= 2;
cnt++; mx = max(mx, edge[i].len);
}
}
if(cnt == 0) return 1;
int k = judge(1, 0, cnt, mx - x);
return flag;
}
signed main(){
n = in; m = in;
for(int i = 1; i < n; i++){
int x = in, y = in, w = in;
add(x, y, w); add(y, x, w);
} prework(1, 0);
// for(int i = 1; i <= n; i++) cout << dis[i] << ' '; puts("");
// for(int i = 1; i <= n; i++) cout << pre[i] << ' '; puts("");
for(int i = 1; i <= m; i++){
edge[i].x = in; edge[i].y = in;
edge[i].lca = LCA(edge[i].x, edge[i].y);
edge[i].len = dis[edge[i].x] + dis[edge[i].y] - 2 * dis[edge[i].lca];
}
int l = 0; int r = INFI;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
return 0;
}