专栏文章

题解:P3258 [JLOI2014] 松鼠的新家

P3258题解参与者 2已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@mippsegm
此快照首次捕获于
2025/12/03 15:57
3 个月前
此快照最后确认于
2025/12/03 15:57
3 个月前
查看原文
这道题目让我们来详细讲一讲树上差分吧。

前置知识

基础差分,模板题点这
最近公共祖先,模板题点这

算法介绍

解决问题类型

在一棵树上有两个点 uuvv,每个点都有自己的点权,需要把在 uuvv 的这条简单路径上包含的所有的点的点权同时加上 zz 或者减少 zz
这就是树上差分主要解决的问题。

实现

容易想到暴力,但是这样暴力修改点权的时间复杂度过高,所以不可取,这下,树上差分就派上用场了(别说什么线段树,树状数组,我不会)。
首先,能写到这题的人肯定已经学会了差分,如果不会请移步模板题。
这里的差分实现主要用图片来解释。
有这么一棵树(每个点旁边的数代表这个点的点权):
现在接到任务,要把 4488 的简单路径上的所有点(包含 4488)的点权加 11,问现在每个点的点权。
cficf_i 为点 ii 与点 i1i-1 的点权差值。
我们先将 44 到根结点,88 到根结点中的点的点权全部加 11,用代码来表示为:
CPP
cf[u]++;
cf[v]++;
修改完的结果如下:
我们发现,结果并不是很理想。
问题出在这么几个地方:
  • 4488 的最近公共祖先,也就是 33 ,点权多了 11
  • 33 的所有祖先结点的点权都多了 22
我们先试着解决第一个问题,想到的方法是从 33 开始,先把到后面的所有点的点权减去 11,用代码表示为:
CPP
int lc = lca(u, v);
//lc存储u和v的最近公共祖先
cf[lc]--;
修改完后:
可以发现,第一个问题已经解决了,此时还剩下的问题:
  • 33 的所有祖先结点的点权都多了 11
那么我们如法炮制,从 33 的父亲开始,把到后面的所有点的点权减去 11,用代码表示为:
CPP
int lc = lca(u, v);
//f[i][j] 表示点i往上跳2^j次会到哪
cf[f[lc][0]]--;
现在修改完后变成了这样:
最后就是这样:
现在是不是没有问题了,那么恭喜你被我恭喜到了学会了树上差分!

树上差分的优点

在实现上,简单,方便。
在时间上,时间复杂度低,每次修改复杂度为 O(1)O(1)

树上差分的注意事项

要用差分数组来求出原序列时,可以用搜索的方法。
设点 xx 的孩子为 yy,则求出 cfxcf_x 的代码为:
CPP
cf[x] = cf[x] + cf[y];
需要注意:这一部分代码应该写在搜索的回溯时,要等 cfycf_y 更新完了才能求出正确的 cfxcf_x

树上差分例题

切入正题

这道题目想让我们干什么?其实就是把 uuvv 的路径上的点的点权增加 11
不过,需要注意的是,一个点 xx 可能既能作为上次参观的终点,也可能是下次参观的起点,需要避免重复。
并且,最后一个房间不用给糖果。
学会了树上差分,这题就很简单了。

代码

终于要结束了(相信你们也看累了)。
最近公共祖先的部分就不打注释了,不会的见模板题。
CPP
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int n, dq[N], cf[N], cg[N], dep[N], f[N][18], lg[N], maxd, maxdq = -1;
vector <int> ed[N];
void dfs(int x, int fa){
	dep[x] = dep[fa] + 1;
	f[x][0] = fa;
	if(dep[x] > maxd) maxd = dep[x];
	for(int i = 0;i < ed[x].size();i++){
		int tod = ed[x][i];
		if(tod != fa){
			dfs(tod, x);
			cf[x] += cf[tod];
		}
	}
}
void qcf(int x, int fa){
	for(int i = 0;i < ed[x].size();i++){
		int tod = ed[x][i];
		if(tod != fa){
			dfs(tod, x);
			cf[x] += cf[tod];
		}
	}
}
int lca(int u, int v){
	if(dep[u] < dep[v]) swap(u, v);
	while(dep[u] > dep[v]){
		u = f[u][lg[dep[u] - dep[v]]];
	}
	if(u == v) return u;
	for(int i = lg[dep[u] - 1];i >= 0;i--){
		if(f[u][i] != f[v][i]){
			u = f[u][i], v = f[v][i];
		}
	}
	return f[u][0];
}
int main(){
	scanf("%d", &n);
	for(int i = 1;i <= n;i++){
		scanf("%d", &cg[i]);
	}
	for(int i = 1;i < n;i++){
		int x, y;
		scanf("%d%d", &x, &y);
		ed[x].push_back(y);
		ed[y].push_back(x);
	}
	dfs(1, 0);
	for(int i = 2;i <= n;i++) lg[i] = lg[i / 2] + 1;
	for(int j = 1;j <= lg[maxd];j++){
		for(int i = 1;i <= n;i++){
			f[i][j] = f[f[i][j - 1]][j - 1];
		}
	}
	//树上差分的部分 
	for(int i = 1;i < n;i++){
		int x = cg[i], y = cg[i + 1], lc;
		lc = lca(x, y);
		cf[x]++, cf[y]++;
		cf[lc]--, cf[f[lc][0]]--;
	}
	qcf(1, 0);
	//要注意避免重复 
	for(int i = 2;i <= n;i++){
		cf[cg[i]]--;
	}
	for(int i = 1;i <= n;i++){
		printf("%d\n", cf[i]);
	}
	return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...