【动态树】弹飞绵羊

简洁暴力动态树

类似于树链剖分,只不过重边不以子树大小唯一确定,而是随着询问适时改变,从而摆脱静态的限制,可以动态添边删边。

边分为实边与虚边,实边连的称为路径,用splay维护(以保证可以动态加删边),从一节点往根走时将路径上的虚边改为实边,通过splay加速上升,而两点lca为最后一个虚变实的节点。

具体实现时,每个节点要存父亲,而根节点的父亲为此链的最浅处连出去的虚边父亲,虚变实时将新的父亲先旋至它所在的树的根,再将当前节点连至新父亲的右节点(假设维护深度序,一开始我没旋wa了很久,因为双链重合),而原来的右节点就被删除了,他的父亲依旧不变只不过变为虚边父亲。

删边时,将要删节点旋至根,如果是实边则截去左子树,否则直接改父亲。

利用原来自创的双选自顶向下模板,很容易改造为自指定节点向上的,除了旋转不能一行写(感觉像写非递归treap),其他部分反而精简了。

#include <cstdio>
#include <cstdlib>
#include <cstring>
int size[300000],ak[300000],rt[300000],l[300000],r[300000];
int n,ans,m;
inline void updata(int x) {size[x]=size[l[x]]+size[r[x]]+1;}
inline int min(int x,int y) {return (x<y) ? x : y;}
inline void right(int x,int y)
{
  int z=rt[y];
  l[y]=r[x],rt[r[x]]=y,r[x]=y,rt[y]=x;updata(y);
  if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;
  rt[x]=z;
}
inline void left(int x,int y)
{
  int z=rt[y];
  r[y]=l[x],rt[l[x]]=y,l[x]=y,rt[y]=x;updata(y);
  if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;
  rt[x]=z;
}
inline void splay(int x)
{
  for (int y,z;(l[rt[x]]==x)||(r[rt[x]]==x);) {
    y=rt[x],z=rt[y];
    if (l[y]==x) {
      if (l[z]==y) right(y,z);right(x,y);
    }
    else {
      if (r[z]==y) left(y,z);left(x,y);
    }
  }
  updata(x);
}
inline int ask(int x)
{
  int sum=0,k;
  for (;;) {
    splay(x);sum+=size[l[x]]+1;
    if (rt[x]==n+1) break;
    splay(rt[x]);
    r[rt[x]]=x,x=rt[x],updata(x);
  }
  return sum;
}
void init()
{
  int i,x,y,ch,root;
  scanf("%d\n",&n);
  for (i=1;i<=n;i++) scanf("%d\n",&ak[i]),rt[i]=min(n+1,i+ak[i]),size[i]=1;
  scanf("%d\n",&m);
  for (i=1;i<=m;i++) {
    scanf("%d",&ch);
    if (1==ch) {
      scanf("%d\n",&x);x++;
      ans=ask(x);
      printf("%d\n",ans);
    }
    else {
      scanf("%d%d\n",&x,&y);x++;
      root=min(x+ak[x],n+1);
      splay(x);
      if (rt[x]!=root) rt[l[x]]=rt[x],l[x]=0;
      ak[x]=y,rt[x]=min(x+ak[x],n+1),updata(x);
    }
  }
}
int main()
{
  freopen("bounce.in","r",stdin);
  freopen("bounce.out","w",stdout);
    init();
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值