松鼠的新家 - LCA/树剖/树的查分

[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);
}



上一篇:st表、RMQ和LCA


下一篇:LCA —— 最近公共祖先