P3258 [JLOI2014] 松鼠的新家 题解
题目
松鼠的新家是一棵树,前几天刚刚装修了新家,新家有 n n n 个房间,并且有 n n n−1 根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。
松鼠想邀请小熊前来参观,并且还指定一份参观指南,他希望小熊能够按照他的指南顺序,先去 a a a 1,再去 a a a 2,……,最后到 a n an an,去参观新家。可是这样会导致重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。
小熊是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。
因为松鼠参观指南上的最后一个房间 a n an an 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。
输入
第一行一个正整数 n n n,表示房间个数第二行 n n n 个正整数,依次描述 a a a 1, a a a 2,⋯ , a n an an。
接下来 n n n−1 行,每行两个正整数 x x x, y y y,表示标号 x x x 和 y y y 的两个房间之间有树枝相连。
输出
一共 n n n 行,第 i i i 行输出标号为 i i i 的房间至少需要放多少个糖果,才能让小熊有糖果吃。
样例
input
5
1 4 5 3 2
1 2
2 4
2 3
4 5
output
1
2
1
2
1
说明/提示
对于全部的数据,2≤ n n n≤3×10^5,1≤ a i ai ai≤ n n n。
解题思路
本题其实就是求两个点的最近公共祖先
在到达最近公共祖先的路径上的所有点+1
↑ 数据量告诉我们,遍历最近公共祖先的路径一个一个加,必爆无疑
用线段树或者树状数组也无法好好的解决
树上的路径无法形成一个区间
这时树上差分就很有用了
差分的优点
- 算法复杂度超低
- 适用于一切 连续的 “线段”(也就是一段路径)
差分解惑小时间

可以看到,2号节点加的值, 在5号节点减去
可以证明:差分数组的的定义:
a
a
a[
i
i
i ] = a[
i
i
i -
1
1
1 ] + 差分数组[
i
i
i ]
总结出差分的思想方法:
如果有一个区间内的权值发生相同的改变的时候,我们可以采用差分的思想方法
而差分的思想方法在于不直接改变区间内的值,而是改变区间[
L
L
L ,
r
r
r ] 对于 区间 [ 0,
L
L
L - 1 ] & 区间[
r
r
r + 1,
R
R
R]的 相对大小关系
总结出一点:
差分就是相对改变 !
差分就是相对改变!!
差分就是相对改变!!!
我们在树上差分
最后的求值
是累加差分的数组和所有子节点的值
因为
L
C
A
LCA
LCA被加了两遍,所以要减1
而
L
C
A
LCA
LCA的父节点不在路径上
但累加了
L
C
A
LCA
LCA,所以也要减1

鸣谢cxy巨佬对差分的讲解
orz
代码
#include<iostream>
#include<cstdio>
using namespace std;
struct hhx{
int to,next;
}a[1000010];
int n,t,f[500010][33],dep[500010],c[500010],head[500010],d[500010];
void add(int x,int y)
{
a[++t].to = y;
a[t].next = head[x];
head[x] = t;
}
void dfs(int dd,int fa)
{
f[dd][0] = fa;
dep[dd] = dep[fa]+1;
for (int i = 1; i <= 30; i++)
f[dd][i] = f[f[dd][i-1]][i-1];
for (int i = head[dd]; i; i = a[i].next)
if (a[i].to != fa)
dfs(a[i].to,dd);
}
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 qiuhe(int dd,int fa)
{
for (int i = head[dd]; i; i=a[i].next)
if (a[i].to != fa)
{
qiuhe(a[i].to,dd);
d[dd]+=d[a[i].to];
}
}
int main()
{
int x,y;
scanf("%d",&n);
for (int i = 1; i <= n; i++)
scanf("%d",&c[i]);
for (int i = 1; i < n; i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x); //建双向边
}
dfs(1,0); //lca的预处理
for (int i = 1; i < n; i++)
{
x=lca(c[i],c[i+1]); //lca查询
d[f[x][0]]--;
d[x]--;
d[c[i]]++;
d[c[i+1]]++; //差分
}
qiuhe(1,0); //做完差分后求和
for (int i = 2; i <= n; i++) d[c[i]]--; //起点多加的减回去
for (int i = 1; i <= n; i++) printf("%d\n",d[i]);
return 0;
}

这篇博客介绍了P3258 [JLOI2014] 松鼠的新家问题的解题思路,主要涉及树上差分的概念和应用。通过树上差分解决给定树结构中每个节点需要放置糖果的数量,以确保小熊在参观过程中始终有糖果吃。博客详细解释了如何利用差分数组来计算每个节点的最小糖果数量,并强调了差分在处理路径问题上的优势。
512

被折叠的 条评论
为什么被折叠?



