[JLOI2014]松鼠的新家
题目传送门
题目大意:
对给定路径上的所有点进行区间加和,最后进行单点查询。
分析:
一道树上差分裸题,当然也可以用树剖来做,不过因为不用进行修改,树链剖分就有些大材小用了。对于这种不用修改的区间的修改,我们最先能想到差分。
普通差分:
对于数组a[i],如果我们想要进行区间修改与单点查询,最暴力的做法就是n个单点修改,O(1)的单点查询。而这会使得修改的时间复杂度特别高。
而差分则相反,其区间修改的时间复杂度为O(1),而单点查询则为O(n),且当要求输出全部节点的结果时,也仅需全部遍历一次。
差分的原理是通过记录相邻元素的变化量,再通过前缀求和来得到单点的信息。
设b数组为差分数组,a数组为答案数组,则有:
而对于区间修改,我们只需要在起始点的对差分数组进行加(减),再在终点处的后一位置进行减(加)
树上差分:
对于树上的路径修改,我们首先把路径分成两条。先求得其LCA(最近公共祖先),将该路径分割成两条链,再对这两条链进行整体修改。
对于树上的差分,差分数组记录的是 改点答案与子树答案总和的变化,即:
其中,x为当前节点,son[x]为其子树
于是,我们便可通过LCA与树上差分维护路径区间的修改
设 路径为从x到y,o = lca( x , y ) ,要对这条路径上的所有节点加v,那么操作为
这里注意,我们要对O的父亲节点的差分数组做减法,这样才能保证O点也被修改到
本题代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 300010
int tr[maxn<<2];
int head[maxn],to[maxn<<1],nxt[maxn<<1],tot=0;
int son[maxn],fa[maxn],top[maxn],sz[maxn],dep[maxn],idx[maxn],num=0;
int ans[maxn];
int pa[maxn];
int n;
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int x,int f)
{
fa[x]=f;
sz[x]=1;
dep[x]=dep[f]+1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==f) continue;
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
void dfs2(int p,int tp)
{
top[p]=tp;
if(son[p])
dfs2(son[p],tp);
for(int i=head[p];i;i=nxt[i])
{
if(to[i]!=fa[p]&&to[i]!=son[p])
dfs2(to[i],to[i]);
}
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
return x;
}
void dfs_ans(int p)
{
ans[p]=tr[p];
for(int i=head[p];i;i=nxt[i])
{
if(to[i]==fa[p])
continue;
dfs_ans(to[i]);
ans[p]+=ans[to[i]];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",pa+i);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<n;i++)
{
int from=pa[i],end=pa[i+1];
int o=lca(from,end);
tr[o]-=1,tr[fa[o]]-=1,tr[end]+=1,tr[from]+=1;
}
dfs_ans(1);
ans[pa[1]]++;
for(int i=1;i<=n;i++)
printf("%d \n",ans[i]-1);
}