[NOIP2016] 天天爱跑步 LCA 树上差分 线段树

这题是NOIP2016 Day1 T2.。。从昨天晚上搞到今天上午。。我现在很慌。。看题解看了半天看不懂,最后还是wcx daolao讲懂的。

看到树上的路径,很容易想到拆成两条路径,即起点到LCA和LCA到终点。

对于起点S到LCA的,要让位于i点的观察员看到,则需满足deep[i]+w[i]=deep[s],对于每一个观察员来说,deep[i]+w[i]为定值,所以只需在i的子树中找到满足的点即可,考虑到对i的子树进行操作,我们想到dfs序,in[i]到out[i]这段区间即为i的子树。我们可以对每个深度开一棵线段树,对于每个观察员i,只需在deep[i]+w[i]的线段树中统计玩家数量。那么到底如何统计呢。。我们可以用树上差分。对于每一条路径,将起点权值加1,将LCA的父亲的权值减1,这样的前缀和即为在这棵深度线段树上能被观察到的人数。。

对于LCA到终点的路径,只需满足deep[s]-2*deep[lca(s,t)]=w[i]-deep[i],所以只需在深度为w[i]-deep[i]的线段树中寻找。。

注意开线段树时要动态开点,不然果断MLE。而且每次求完上升路径时,要清空线段树

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read()
{
  int x=0,f=1;
  char ch=getchar();
  while(ch<'0'||ch>'9')
  {
    if(ch=='-')
      f=-1;
    ch=getchar();
  }
  while(ch>='0'&&ch<='9')
  {
    x=(x<<3)+(x<<1)+ch-'0';
    ch=getchar();
  }
  return x*f;
}
struct bian
{
  int qi,zhong,next;
};
bian c[600010];
bool vis[300010];
int n,m,x,y,jishu=0,jishu1=0,jishu2=0;
int in[300010],ans[300010],out[300010],zu[300010],xuan[300010],ying[300010],sum[7000000],lc[7000000],rc[7000000],root[7000000];
int w[300010],head[300010],deep[300010],size[300010],fa[300010],p[300010][20];
void add(int x,int y)
{
  c[++jishu].qi=x;
  c[jishu].zhong=y;
  c[jishu].next=head[x];
  head[x]=jishu;
}
void init()
{
  for(int j=0;(1<<j)<=n;++j)
    for(int i=1;i<=n;++i)
      p[i][j]=-1;
  for(int i=1;i<=n;++i)
    p[i][0]=fa[i];
  for(int j=1;(1<<j)<=n;++j)
    for(int i=1;i<=n;++i)
      if(p[i][j-1]!=-1)
        p[i][j]=p[p[i][j-1]][j-1];
}
void dfs(int u,int f,int t)
{
  deep[u]=t;
  fa[u]=f;
  for(int i=head[u];i;i=c[i].next)
    if(c[i].zhong!=f)
      dfs(c[i].zhong,u,t+1);
}
int lca(int a,int b)
{
  int i,j;
  if(deep[a]<deep[b])
    swap(a,b);
  for(i=0;(1<<i)<=deep[a];++i);
  i--;
  for(j=i;j>=0;--j)
    if(deep[a]-(1<<j)>=deep[b])
      a=p[a][j];
  if(a==b)  return a;
  for(j=i;j>=0;--j)
    if(p[a][j]!=-1&&p[a][j]!=p[b][j])
    {
      a=p[a][j];
      b=p[b][j];
    }
  return fa[a];
}
void dfs1(int x)
{
  in[x]=++jishu1;
  vis[x]=1;
  for(int i=head[x];i;i=c[i].next)
    if(!vis[c[i].zhong])
      dfs1(c[i].zhong);
  out[x]=jishu1;
}
void clear()
{
  jishu2=0;
  memset(lc,0,sizeof(lc));
  memset(rc,0,sizeof(rc));
  memset(sum,0,sizeof(sum));
  memset(root,0,sizeof(root));
}
void update(int l,int r,int i,int w,int &now)
{
  if(!i)  return ;
  if(!now)  now=++jishu2;
  sum[now]+=w;
  if(l==r)  return ;
  int mid=(l+r)>>1;
  if(i<=mid)
    update(l,mid,i,w,lc[now]);
  else
    update(mid+1,r,i,w,rc[now]);
}
int query(int l,int r,int L,int R,int i)
{
  if(!i)  return 0;
  if(L<=l&&r<=R)  return sum[i];
  int mid=(l+r)>>1;
  if(R<=mid)  return query(l,mid,L,R,lc[i]);
  else  if(L>mid)  return query(mid+1,r,L,R,rc[i]);
  else  return query(l,mid,L,mid,lc[i])+query(mid+1,r,mid+1,R,rc[i]);
}
int main()
{
  n=read();m=read();
  for(int i=1;i<n;++i)
  {
    x=read();y=read();
    add(x,y);add(y,x);
  }
  dfs(1,-1,0);
  dfs1(1);
  init();
  for(int i=1;i<=n;++i)
    w[i]=read();
  for(int i=1;i<=m;++i)
  {
    xuan[i]=read();ying[i]=read();
    zu[i]=lca(xuan[i],ying[i]);
  }
  for(int i=1;i<=m;++i)
  {
    int shen=deep[xuan[i]];
    update(1,n,in[xuan[i]],1,root[shen]);
    update(1,n,in[p[zu[i]][0]],-1,root[shen]);
  }
  for(int i=1;i<=n;++i)
    ans[i]+=query(1,n,in[i],out[i],root[deep[i]+w[i]]);
  clear();
  for(int i=1;i<=m;++i)
  {
    int shen=deep[xuan[i]]-2*deep[zu[i]]+2*n;
    update(1,n,in[ying[i]],1,root[shen]);
    update(1,n,in[zu[i]],-1,root[shen]);
  }
  for(int i=1;i<=n;++i)
    ans[i]+=query(1,n,in[i],out[i],root[w[i]-deep[i]+2*n]);
  for(int i=1;i<=n;++i)
  {
    if(i!=n)
      printf("%d ",ans[i]);
    else
      printf("%d",ans[i]);
  }
  return 0;
}
    


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NOIP(全国信息学奥林匹克竞赛)是中国的一项信息学竞赛活动,旨在选拔和培养优秀的计算机程序设计人才。树是NOIP的一个基础知识点,具体内容如下: 树是一种非线性的数据结构,由n(n>=0)个节点组成,节点之间通过有限数量的边连接起来。树的特点是:每个节点(除根节点外)只有一个父节点,而可以有多个子节点。根节点是树的起始节点,没有父节点。树中的每个节点都有一个值,可以存储任意类型的数据。 树可以用于解决各种计算机问题,例如构建文件系统、网络路由等。在NOIP竞赛中,树常常用于解决图论、动态规划等算法问题。 树的常见概念包括: 1. 节点:树的基本单元,包含值和指向子节点的指针。 2. 根节点:树的起始节点,没有父节点。 3. 叶节点:没有子节点的节点。 4. 父节点:某个节点直接指向当前节点的节点。 5. 子节点:当前节点直接指向的节点。 6. 树的高度:树中从根节点到最远叶节点的边的数量。也可以定义为最深叶节点的层数加一。 7. 子树:树中的一个节点及其所有子节点组成的树。 在解决NOIP问题时,我们需要掌握树的遍历方法,包括: 1. 先序遍历:先访问根节点,然后按先序遍历的顺序递归访问左子树和右子树。 2. 中序遍历:先递归访问左子树,然后访问根节点,最后递归访问右子树。 3. 后序遍历:先递归访问左子树,然后递归访问右子树,最后访问根节点。 此外,我们还需要了解树的相关算法和数据结构,如二叉树、二叉搜索树、平衡树、堆等。这些树的变种在NOIP竞赛中也经常出现。 总之,树是NOIP竞赛的一个基础知识点,掌握树的概念、特点、遍历方法以及相关算法和数据结构,对于解决竞赛中的问题非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值