树上差分啊

树上差分

大概的介绍

  树上的差分和普通的区间差分其实差不多,都是解决离线多次修改最后一次查询的问题的(不要告诉我你不会数组差分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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值