树上差分小结

这是一个把差分思想运用到树上的优秀技巧,他和差分数组很像,常数小,还好写,并且思想也很简单。

点差分

树上有两种东西,点和边,他们都可以带权,所以自然也就有两种不同的差分方式来维护他们。

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]=yxb[y],其中, y ∈ x y\in x yx 表示 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 
#include 
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)(尴尬的是我并没有用树上差分做这题)
这两题的题解:运输计划 天天爱跑步

你可能感兴趣的:(算法小结区)