[BZOJ 4573][ZJOI 2016]大♂森林

[LOJ 2092][BZOJ 4573][UOJ 195][ZJOI 2016]大♂森林

题意

给定一个树序列, 初始时所有树都只有一个点, 要求支持三种操作:

  1. 区间种树(在某个特定点上长出一个子结点)
  2. 区间更改种树点(就是改上面那个操作中的「特定点」)
  3. 查询某棵树上两个点间的距离

\(n\le 1\times 10^5, q\le 2\times 10^5\). 不强制在线. 长出来的点标号一致, 与种树操作的顺序一致. 保证2操作合法.

题解

ZJOI都是神仙题啊QAQ...

首先这题序列上的元素是树...一棵树得占它个 \(O(n)\) 级别的空间所以肯定得想办法离线之后一个一个处理...

其次我们发现好像对于已经长出来的点, 后面再怎么修改也不会和它有什么关系了. 于是我们可以把重点放在前两个操作而把 2 操作丢到最后等树形态构造完成之后再搞.

还有一点就是其实多长出来的点并不会影响答案(因为保证 2 操作合法, 而不合法的点一定不会是合法的点的祖先), 所以其实可以考虑维护一整棵包含所有 0 操作生长出来的结点的树. 这样我们就可以避免一直添点删点的问题了.

也就是说我们现在只剩下最辣手的操作 1 了.

考虑从第 \(i\) 棵树的形态如何快速转化为第 \(i+1\) 棵的形态.

如果这两棵树形态不同, 必然是一棵执行了某个操作 1 而另一个没有. 假设原生长点是 \(u\), 新生长点是 \(v\), 则在操作 1 之后, 原来长在 \(u\) 下面的所有点实际上都应该挂在 \(v\) 下面才对. 所以实际上就是一个子树移动的过程.

子树移动? Cut一下再Link一下就完了?

假的...

现在的需求是将某个点下面的所有子树都移动走, 但是它有多少子树可不一定...

我们可以放一个虚点来把它们收束起来. 预处理的时候对每个 1 操作建立一个虚点, 距离这个操作最近的挂上去的点都挂在这个虚点上. 这样如果这个 1 操作产生了子树移动, 直接把虚点切下来就好了.

什么你说虚点会导致距离增加? 裆燃是把虚点的 size 设为 0 辣~

一些小的注意事项

  1. 注意LCT查询LCA的正确姿势以及不同Access写法的不同副作用. (\(0\text{pts}\rightarrow40\text{pts}\))
  2. 注意每个实点都有一个存在区间, 如果要把某个子树挂到 \(v\) 上需要检查 \(v\) 在哪一段中存在. 不存在的不能理会. (\(40\text{pts}\rightarrow70\text{pts}\))
  3. 注意 1 号点的存在区间要初始化为 \([1,n]\). (\(70\text{pts}\rightarrow100\text{pts}\))

参考代码

#include <bits/stdc++.h>
#define _O0 __attribute__((optimize("O0"))) // 防止沙雕编译器把this当成非空来优化

const int MAXN=3e5+10;

struct Query{
    int type;
    int time;
    int u;
    int v;
    int r;
    bool friend operator<(const Query& a,const Query& b){
        return std::make_pair(a.type,a.time)<std::make_pair(b.type,b.time);
    }
};

struct Dump{
    int t;
    int a;
    int b;
    int c;
};

#define lch chd[0]
#define rch chd[1]
#define kch chd[k]
#define xch chd[k^1]

struct Node{
    int v;
    int sz;
    bool rev;
    Node* prt;
    Node* pprt;
    Node* chd[2];
    Node(int v):v(v),sz(v),rev(false),prt(NULL),pprt(NULL),chd{NULL,NULL}{}
    inline _O0 int size(){
        return this==NULL?0:this->sz;
    }
    inline void Maintain(){
        this->sz=this->lch->size()+this->rch->size()+this->v;
    }
    inline _O0 void Flip(){
        if(this!=NULL){
            std::swap(this->lch,this->rch);
            this->rev=!this->rev;
        }
    }
    inline void PushDown(){
        if(this->rev){
            this->lch->Flip();
            this->rch->Flip();
            this->rev=false;
        }
    }
};
Node* N[MAXN];

void Rotate(Node* root,int k){
    Node* tmp=root->xch;
    root->PushDown();
    tmp->PushDown();
    tmp->prt=root->prt;
    if(root->prt==NULL){
        tmp->pprt=root->pprt;
        root->pprt=NULL;
    }
    else if(root->prt->lch==root)
        root->prt->lch=tmp;
    else
        root->prt->rch=tmp;
    root->xch=tmp->kch;
    if(root->xch!=NULL)
        root->xch->prt=root;
    tmp->kch=root;
    root->prt=tmp;
    root->Maintain();
    tmp->Maintain();
}

void Splay(Node* root){
    while(root->prt!=NULL){
        int k=root->prt->lch==root;
        if(root->prt->prt==NULL)
            Rotate(root->prt,k);
        else{
            int d=root->prt->prt->lch==root->prt;
            Rotate(k==d?root->prt->prt:root->prt,k);
            Rotate(root->prt,d);
        }
    }
}

void Expose(Node* root){
    Splay(root);
    root->PushDown();
    if(root->rch){
        root->rch->pprt=root;
        root->rch->prt=NULL;
        root->rch=NULL;
        root->Maintain();
    }
}

Node* Access(Node* root){
    Node* dump=root;
    Node* tmp=NULL;
    while(root){
        Expose(root);
        root->rch=tmp;
        if(tmp){
            tmp->pprt=NULL;
            tmp->prt=root;
        }
        root->Maintain();
        tmp=root;
        root=root->pprt;
    }
    Splay(dump);
    return tmp;
}

void Evert(Node* root){
    Access(root);
    root->Flip();
}

Node* FindRoot(Node* root){
    Access(root);
    Node* ans=root;
    while(ans->lch)
        ans=ans->lch;
    Splay(ans);
    return ans;
}

int Calc(int a,int b){
    if(FindRoot(N[a])!=FindRoot(N[b]))
        return -1;
    int ans=0;
    Access(N[a]);
    ans+=N[a]->size();
    Node* lca=Access(N[b]);
    ans+=N[b]->size();
//  printf("%d\n",ans);
    Access(lca);
    ans-=2*lca->size();
//  printf("%d %d lca=%p\n",a,b,lca);
    return ans;
}

void Link(int x,int y){
//  printf("link %d <- %d\n",x,y);
    Evert(N[y]);
    N[y]->pprt=N[x];
}

void Cut(int x){
//  printf("cut %d\n",x);
    Evert(N[1]);
    Access(N[x]);
    N[x]->PushDown();
    N[x]->lch->prt=NULL;
    N[x]->lch=NULL;
    N[x]->Maintain();
}

int n;
int q;
int cnt;
int L[MAXN];
int R[MAXN];
Dump D[MAXN];
int ans[MAXN];
int prt[MAXN];
int alive[MAXN];
std::vector<Query> Q[MAXN];

int main(){
    scanf("%d%d",&n,&q);
    cnt=1;
    N[0]=new Node(0);
    N[1]=new Node(1);
    Link(1,0);
    L[1]=1,R[1]=n;
    prt[0]=1;
    prt[1]=-1;
    for(int i=0;i<q;i++){
        scanf("%d%d%d",&D[i].t,&D[i].a,&D[i].b);
        if(D[i].t!=0)
            scanf("%d",&D[i].c);
        else{
            N[++cnt]=new Node(1);
            L[cnt]=D[i].a;
            R[cnt]=D[i].b;
        }
    }
    int last=0;
    int cur=1;
    for(int i=0;i<q;i++){
        if(D[i].t==0){
            prt[++cur]=last;
            Link(last,cur);
        }
        else if(D[i].t==1){
            int l=std::max(L[D[i].c],D[i].a),r=std::min(R[D[i].c],D[i].b);
            if(l>r)
                continue;
            N[++cnt]=new Node(0);
            Link(last,cnt);
            prt[cnt]=last;
            last=cnt;
            Q[l].push_back({1,i,cnt,D[i].c,r+1});
        }
        else
            Q[D[i].a].push_back({2,i,D[i].b,D[i].c,0});
    }
//  for(int i=0;i<=cnt;i++)
//      printf("N[%d]=%p\n",i,N[i]);
    for(int i=1;i<=n;i++){
        std::sort(Q[i].begin(),Q[i].end());
        for(auto q:Q[i]){
            if(q.type==1){
                if(q.r)
                    Q[q.r].push_back({1,q.time,q.u,prt[q.u],0});
                Cut(q.u);
                Link(q.v,q.u);
                prt[q.u]=q.v;
            }
            else{
            //  assert(q.type==2);
                ans[q.time]=Calc(q.u,q.v);
            }
        }
    }
    for(int i=0;i<q;i++)
        if(D[i].t==2)
            printf("%d\n",ans[i]);
    return 0;
}

1200714-20171020063000599-655577482.png

转载于:https://www.cnblogs.com/rvalue/p/10365321.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值