bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)

Posted zinn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)相关的知识,希望对你有一定的参考价值。

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779

RELEASE操作可以对应LCT的 access,RECENTER则是 makeroot;

考虑颜色数,把一条实边变成虚边,子树+1,虚变实子树-1;

但有换根操作,怎么维护子树?

也可以用 dfs 序线段树维护,其实换 rt 只是 splay 的根方向改变,对应的子树还是可以找到的;

注意虚边变实或实边变虚时要找子树,不是直接找那个儿子,而是找那个儿子所在 splay 的根;

然后这里 splay 的 reverse 就要打上标记的同时已经交换子树,否则找儿子时可能会错(?);

一开始写了树剖+线段树的复杂版本,总是不对...

技术分享图片
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
#define ls (x<<1)
#define rs (x<<1|1)
using namespace std;
int const xn=1e5+5;
int n,rt,pre[xn],c[xn][2],sta[xn],tp,rev[xn],hd[xn],ct,to[xn<<1],nxt[xn<<1];
int tim,fa[xn],dfn[xn],sum[xn<<1],lzy[xn<<1],top[xn],siz[xn],son[xn],dep[xn];
char dc[20];
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<0||ch>9){if(ch==-)f=0; ch=getchar();}
  while(ch>=0&&ch<=9)ret=ret*10+ch-0,ch=getchar();
  return f?ret:-ret;
}
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
void turn(int x,int s,int len){sum[x]+=s*len; lzy[x]+=s;}//*len
void pushdown(int x,int l,int r)
{
  if(!lzy[x])return;
  turn(ls,lzy[x],mid-l+1); turn(rs,lzy[x],r-mid);
  lzy[x]=0;
}
void pushup(int x){sum[x]=sum[ls]+sum[rs];}
void update(int x,int l,int r,int L,int R,int s)
{
  if(L>R)return;
  if(l>=L&&r<=R){turn(x,s,r-l+1); return;}
  pushdown(x,l,r);
  if(mid>=L)update(ls,l,mid,L,R,s);
  if(mid<R)update(rs,mid+1,r,L,R,s);
  pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
  if(L>R)return 0;
  if(l>=L&&r<=R)return sum[x];
  pushdown(x,l,r); int ret=0;
  if(mid>=L)ret+=query(ls,l,mid,L,R);
  if(mid<R)ret+=query(rs,mid+1,r,L,R);
  return ret;
}
void dfs(int x,int ff)
{
  fa[x]=pre[x]=ff;//self splay
  dep[x]=dep[ff]+1; siz[x]=1;
  for(int i=hd[x],u;i;i=nxt[i])
    if((u=to[i])!=ff)
      {
    dfs(u,x); siz[x]+=siz[u];
    if(siz[u]>siz[son[x]])son[x]=u;
      }
}
void dfs2(int x)
{
  dfn[x]=++tim;
  update(1,1,n,dfn[x],dfn[x],dep[x]);
  if(son[x])top[son[x]]=top[x],dfs2(son[x]);
  for(int i=hd[x],u;i;i=nxt[i])
    if((u=to[i])!=son[x]&&u!=fa[x])top[u]=u,dfs2(u);
}
void torev(int x){rev[x]^=1; swap(c[x][0],c[x][1]);}//
void reverse(int x)
{
  if(!rev[x])return;
  //swap(c[x][0],c[x][1]);
  //rev[c[x][0]]^=1; rev[c[x][1]]^=1;
  if(c[x][0])torev(c[x][0]);
  if(c[x][1])torev(c[x][1]);
  rev[x]=0;
}
bool isroot(int x){return c[pre[x]][0]!=x&&c[pre[x]][1]!=x;}
void rotate(int x)
{
  int y=pre[x],z=pre[y],d=(c[y][1]==x);
  if(!isroot(y))c[z][c[z][1]==y]=x;
  pre[x]=z; pre[y]=x; pre[c[x][!d]]=y;
  c[y][d]=c[x][!d]; c[x][!d]=y;
}
void splay(int x)
{
  //reverse!
  sta[tp=1]=x;
  for(int i=x;!isroot(i);i=pre[i])sta[++tp]=pre[i];
  while(tp)reverse(sta[tp--]);
  while(!isroot(x))
    {
      int y=pre[x],z=pre[y];
      if(!isroot(y))
    {
      if((c[y][0]==x)^(c[z][0]==y))rotate(x);
      else rotate(y);
    }
      rotate(x);
    }
}
int find(int x,int y)//y in xtree
{
  //while(top[y]!=top[x])y=fa[top[y]];//?
  while(dep[x]<dep[top[y]])
    {
      y=top[y];
      if(fa[y]==x)return y;
      y=fa[y];
    }
  //while(fa[y]!=x)y=fa[y];
  //return y;
  return son[x];//
}
void change(int x,int v)
{
  if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-1)
    {
      int y=find(x,rt);
      update(1,1,n,1,dfn[y]-1,v);
      update(1,1,n,dfn[y]+siz[y],n,v);
    }
  else update(1,1,n,dfn[x],dfn[x]+siz[x]-1,v);
}
int findrt(int x)
{
  while(c[x][0])x=c[x][0];
  return x;
}
void access(int x)
{
  bool fl=0;
  for(int t=0;x;x=pre[x])
    {
      splay(x);
      if(c[x][1])change(findrt(c[x][1]),1);//
      c[x][1]=t;
      if(t)change(findrt(t),-1);//
      t=x;
    }
}
void makeroot(int x)
{
  access(x); splay(x); torev(x);//
}
int main()
{
  n=rd(); int m=rd();
  for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x);
  rt=1; dfs(1,0); top[1]=1; dfs2(1);
  for(int i=1,x;i<=m;i++)
    {
      scanf("%s",dc); scanf("%d",&x);
      if(dc[2]==L)access(x);
      if(dc[2]==C)makeroot(x),rt=x;
      if(dc[2]==Q)
    {
      int ret=0,sz;
      if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-1)//in subtree
        {
          int y=find(x,rt); sz=n-siz[y];
          ret+=query(1,1,n,1,dfn[y]-1);
          ret+=query(1,1,n,dfn[y]+siz[y],n);
        }
      else if(rt==x)ret=query(1,1,n,1,n),sz=n;//!!!
      else ret=query(1,1,n,dfn[x],dfn[x]+siz[x]-1),sz=siz[x];
      printf("ret=%d sz=%d
",ret,sz);
      printf("%.7f
",1.0*ret/sz);
    }
    }
  return 0;
}

然后听说线段树会被卡,要写树状数组,于是干脆把那个未知错误的代码扔了;

但是...树状数组维护序列可以单点修改区间查询,维护差分序列可以区间修改单点查询,如何区间修改区间查询?

找题解,学到新知识了——区间修改区间查询的树状数组!

树状数组里放两个数组,一个维护差分序列 ( d[i] ),另一个维护 ( d[i]*i );

为什么这样?因为首先,要区间修改,只能维护差分数组;

考虑如何从差分数组得到原数组的前缀和 ( s[p] ),就是 ( sumlimits_{i=1}^{p} sumlimits_{j=1}^{i} d[j] )

发现一个 ( d[j] ) 被算了 ( p-j+1 ) 次,也就是 ( (p+1)-j ) 次;

所以只要维护 ( d[i] ) 和 ( d[i]*i ),到时候查询 ( p ) 位置的原序列前缀和,就是 ( (p+1)*sumlimits_{i=1}^{p} d[i] - sumlimits_{i=1}^{p}d[i]*i );

所以这个似乎很好用的样子;

一晚上栽在 splay 之前的一系列 reverse 上,又分不清 i 和 x 了呵呵,然而实际上这题做了一天。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int const xn=1e5+5;
int n,m,rt,hd[xn],ct,to[xn<<1],nxt[xn<<1],sta[xn],top,rev[xn],dep[xn];
int tim,fa[xn][20],pre[xn],c[xn][2],dfn[xn],ed[xn];
ll s[xn],g[xn];
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<0||ch>9){if(ch==-)f=0; ch=getchar();}
  while(ch>=0&&ch<=9)ret=ret*10+ch-0,ch=getchar();
  return f?ret:-ret;
}
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
void update(int x,ll v){for(int i=x;i<=n;i+=(i&-i))s[i]+=v,g[i]+=x*v;}//g[i]+=x*v
ll query(int x)
{
  if(x>n)return 0;
  ll s1=0,s2=0;
  for(int i=x;i;i-=(i&-i))s1+=s[i],s2+=g[i];
  return (x+1)*s1-s2;
  }
void dfs(int x,int ff)
{
  pre[x]=fa[x][0]=ff;
  for(int i=1;fa[fa[x][i-1]][i-1];i++)fa[x][i]=fa[fa[x][i-1]][i-1];
  dfn[x]=++tim; dep[x]=dep[ff]+1;
  for(int i=hd[x],u;i;i=nxt[i])
    if((u=to[i])!=ff)dfs(u,x);
  ed[x]=tim;
  update(dfn[x],1); update(ed[x]+1,-1);
}
bool isroot(int x){return c[pre[x]][0]!=x&&c[pre[x]][1]!=x;}
void rever(int x)
{
  rev[x]^=1;
  swap(c[x][0],c[x][1]);
}
void pushdn(int x)
{
  if(!rev[x])return;
  rever(c[x][0]); rever(c[x][1]);
  rev[x]=0;
}
void rotate(int x)
{
  int y=pre[x],z=pre[y],d=(c[y][1]==x);
  if(!isroot(y))c[z][c[z][1]==y]=x;
  pre[x]=z; pre[y]=x; pre[c[x][!d]]=y;
  c[y][d]=c[x][!d]; c[x][!d]=y;
}
void splay(int x)
{
  sta[top=1]=x;
  for(int i=x;!isroot(i);i=pre[i])sta[++top]=pre[i];//i!!!
  while(top)pushdn(sta[top--]);
  while(!isroot(x))
    {
      int y=pre[x],z=pre[y];
      if(!isroot(y))
    ((c[y][0]==x)^(c[z][0]==y))?rotate(x):rotate(y);
      rotate(x);
    }
    }
int find(int x,int y)//y in x‘s subtree
{
  for(int i=19;i>=0;i--)
    if(dep[fa[y][i]]>dep[x])y=fa[y][i];
  return y;
}
void change(int x,int v)
{
  while(c[x][0])pushdn(x),x=c[x][0];
  if(rt==x)update(1,v);
  else if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x])
    {
      int y=find(x,rt);
      update(1,v); update(dfn[y],-v); update(ed[y]+1,v);
    }
  else update(dfn[x],v),update(ed[x]+1,-v);
}
void access(int x)
{
  for(int t=0;x;x=pre[x])
    {
      splay(x);
      if(c[x][1])change(c[x][1],1);
      c[x][1]=t;
      if(t)change(t,-1);
      t=x;
    }
}
void makeroot(int x)
{
  access(x); splay(x); rever(x);
}
double getans(int x)
{
  if(rt==x)return 1.0*query(n)/n;
  if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x])
    {
      int y=find(x,rt);
      ll ret=query(n)-query(ed[y])+query(dfn[y]-1);
      return 1.0*ret/(n-(ed[y]-dfn[y]+1));
    }
  ll ret=query(ed[x])-query(dfn[x]-1);
  return 1.0*ret/(ed[x]-dfn[x]+1);
}
char dc[20];
int main()
{
  n=rd(); m=rd();
  for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x);
  dfs(1,0);
  for(int i=1,x;i<=m;i++)
    {
      scanf("%s",dc); x=rd();
      if(dc[2]==L)access(x);
      if(dc[2]==C)makeroot(x),rt=x;
      if(dc[2]==Q)printf("%.10f
",getans(x));
    }
  return 0;
}

 

以上是关于bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ3779重组病毒 LCT+DFS序

bzoj 3779 重组病毒——LCT维护子树信息

BZOJ 3779 重组病毒 LCT+线段树(维护DFS序)

BZOJ-3779重组病毒 LinkCutTree + 线段树 + DFS序

3779: 重组病毒

bzoj4817[Sdoi2017]树点涂色&&bzoj3779-重组病毒