题解:P3258 [JLOI2014] 松鼠的新家
这道题目让我们来详细讲一讲树上差分吧。
前置知识
基础差分,模板题点这。\ 最近公共祖先,模板题点这。
算法介绍
解决问题类型
在一棵树上有两个点
这就是树上差分主要解决的问题。
实现
容易想到暴力,但是这样暴力修改点权的时间复杂度过高,所以不可取,这下,树上差分就派上用场了(别说什么线段树,树状数组,我不会)。
首先,能写到这题的人肯定已经学会了差分,如果不会请移步模板题。
这里的差分实现主要用图片来解释。
有这么一棵树(每个点旁边的数代表这个点的点权):
现在接到任务,要把
记
我们先将
cf[u]++;
cf[v]++;
修改完的结果如下:
我们发现,结果并不是很理想。
问题出在这么几个地方:
我们先试着解决第一个问题,想到的方法是从
int lc = lca(u, v);
//lc存储u和v的最近公共祖先
cf[lc]--;
修改完后:
可以发现,第一个问题已经解决了,此时还剩下的问题:
那么我们如法炮制,从
int lc = lca(u, v);
//f[i][j] 表示点i往上跳2^j次会到哪
cf[f[lc][0]]--;
现在修改完后变成了这样:
最后就是这样:
现在是不是没有问题了,那么恭喜你被我恭喜到了学会了树上差分!
树上差分的优点
在实现上,简单,方便。
在时间上,时间复杂度低,每次修改复杂度为
树上差分的注意事项
要用差分数组来求出原序列时,可以用搜索的方法。
设点
cf[x] = cf[x] + cf[y];
需要注意:这一部分代码应该写在搜索的回溯时,要等
树上差分例题
P3128
P3258
切入正题
这道题目想让我们干什么?其实就是把
不过,需要注意的是,一个点
并且,最后一个房间不用给糖果。
学会了树上差分,这题就很简单了。
代码
终于要结束了(相信你们也看累了)。
最近公共祖先的部分就不打注释了,不会的见模板题。
#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;
}