这是一个把差分思想运用到树上的优秀技巧,他和差分数组很像,常数小,还好写,并且思想也很简单。
点差分
树上有两种东西,点和边,他们都可以带权,所以自然也就有两种不同的差分方式来维护他们。
设 v a l [ x ] val[x] val[x] 表示点 x x x 的权值, b [ x ] b[x] b[x] 表示 x x x 处差分数组的权值,差分的对象是自己的所有儿子,那么就有 v a l [ x ] = ∑ y ∈ x b [ y ] val[x]=\sum_{y\in x} b[y] val[x]=∑y∈xb[y],其中, y ∈ x y\in x y∈x 表示 y y y 在 x x x 的子树内( x x x 也在自己的子树内)。
如果要让树上
x
x
x 到
y
y
y 路径上的点权值
+
1
+1
+1,设
l
c
a
lca
lca 为
x
x
x 和
y
y
y 的最近公共祖先,以及设
f
a
fa
fa 为
l
c
a
lca
lca 的父亲节点,那么只需要让
b
[
x
]
b[x]
b[x] 和
b
[
y
]
b[y]
b[y] 加一,然后让
b
[
l
c
a
]
b[lca]
b[lca] 和
b
[
f
a
]
b[fa]
b[fa] 减一即可,因为可以发现,一个节点的
b
b
b 的改变,只会影响到该节点到根节点路径上
的点的
v
a
l
val
val。
边差分
可以发现,每一条边连接着一个父亲和一个儿子,可能有多条边连接同一个父亲,但是每条边肯定只对应一个儿子,于是我们不妨将每条边的 b b b 放到自己对应的儿子上。
那么修改时,类似的,让 b [ x ] b[x] b[x] 和 b [ y ] b[y] b[y] 加一,然后让 b [ l c a ] b[lca] b[lca] 减二即可。求值的时候一样是对子树求和。
很显然,每条路线相当于给树上的一条路径上的每个点
权值
+
1
+1
+1,然后找出权值最大的点即可。
代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100010
int n,m;
struct edge{int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int f[maxn][20],b[maxn],deep[maxn];
void dfs(int x,int fa)
{
f[x][0]=fa;deep[x]=deep[fa]+1;
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa)dfs(e[i].y,x);
}
int lca(int x,int y)
{
if(deep[x]>deep[y])swap(x,y);
for(int i=19;i>=0;i--)if(deep[f[y][i]]>=deep[x])y=f[y][i];
if(x!=y){for(int i=19;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];x=f[x][0];}return x;
}
int ans=0;
void get_ans(int x){
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=f[x][0]){get_ans(e[i].y);b[x]+=b[e[i].y];ans=max(ans,b[x]);}
}
int main()
{
scanf("%d %d",&n,&m);for(int i=1,x,y;i<n;i++)
scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);dfs(1,0);
for(int j=1;j<=19;j++)for(int i=1;i<=n;i++)f[i][j]=f[f[i][j-1]][j-1];
for(int i=1,x,y,z;i<=m;i++)scanf("%d %d",&x,&y),z=lca(x,y),b[x]++,b[y]++,b[z]--,b[f[z][0]]--;
get_ans(1);printf("%d",ans);
}
更多好题:
运输计划(提高组2015)
天天爱跑步(提高组2016)(尴尬的是我并没有用树上差分做这题)
这两题的题解:运输计划 天天爱跑步