BZOJ4530: [Bjoi2014]大融合(LCT维护子树,线段树合并)

传送门

题意:
给一个动态树,维护sze集合大小。

题解:
动态树。

LCT维护子树和的方法(不支持子树修改,其实实现起来也挺简单的):

对于维护这一类满足加减的信息,可以考虑在 LCT 的每个点分别维护出所有虚边和实边连向他和。
如果得到了维护,那么可以轻松在 O(logn) 时间内完成询问。
考虑怎么维护:
首先 Splay 不会改变虚边的性质,那么 Splay 中直接动态调整实边的和就好了。
对于 Access 操作,显然断开右儿子会导致虚实边切换,可以在维护的东西上简单加减就好了。不过需要注意的是,加减最好在当前点是其所在 Splay 的根节点的时候才能加减,否则该点的和改变后其父节点的和也会相应改变(只有实父亲),还要向上 update (虚父亲不用更新是因为 Access 操作会一路向上走,总会更新到虚父亲的)。
对于 Link 操作,直接连边也会导致一样的错误,那么先将两边的点都置为根节点就好了。
对于每组查询操作,先将查询点 Access ,此时其虚子树和包含了其所有子树,可以加上自己的信息而得到答案。

关于维护最大最小值也可以用类似的方法。不过需要注意的是不能简单的加减,容易想到增加一个 logn 的时间复杂度,对每个点维护multiset来完成删除操作。

  • Code
#include<bits/stdc++.h>
using namespace std;
inline int rd(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
inline void W(long long x){
    static int buf[50];
    if(!x){putchar('0');return;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]){putchar(buf[buf[0]--]+'0');}
}
const int Maxn=1e5+50;
int n,m;
struct node{
    node *lc,*rc,*fa;
    int sze_v,sze_t,revtag;
    node():lc(NULL),rc(NULL),fa(NULL),sze_t(1){}
    inline void rev();inline void upt();inline void pd();
}Pool[Maxn],*pool=Pool;
inline void node::upt(){sze_t=(lc?lc->sze_t+lc->sze_v:0)+(rc?rc->sze_t+rc->sze_v:0)+1;}
inline void node::rev(){revtag^=1;swap(lc,rc);}
inline bool isroot(node *x){return !x->fa||(x->fa->lc!=x&&x->fa->rc!=x);}
inline bool which(node *x){return x->fa->lc==x;}
inline void node::pd(){
    if(!isroot(this))fa->pd();
    if(revtag){
        if(lc)lc->rev();if(rc)rc->rev();
        revtag=0;
    }
}
inline void rotate(node *x){
    node *y=x->fa,*z=y->fa;
    if(!isroot(y))((z->lc==y)?z->lc:z->rc)=x;
    x->fa=z;y->fa=x;
    if(y->lc==x){
        node *b=x->rc;
        x->rc=y;y->lc=b;
        if(b)b->fa=y;
    }else{
        node *b=x->lc;
        x->lc=y;y->rc=b;
        if(b)b->fa=y;
    }
    y->upt();x->upt();
}
inline void splay(node *x){
    x->pd();
    while(!isroot(x)){
        node *y=x->fa;
        if(!isroot(y)){
            (which(y)^which(x))?(rotate(x)):(rotate(y));
        }rotate(x);
    }
}
inline void access(node *x){
    for(node *t=NULL;x;t=x,x=x->fa){
        splay(x);int rc_sze=(x->rc)?(x->rc->sze_t+x->rc->sze_v):0;
        x->sze_t-=rc_sze;x->sze_v+=rc_sze;
        x->rc=t;rc_sze=(x->rc)?(x->rc->sze_t+x->rc->sze_v):0;
        x->sze_t+=rc_sze;x->sze_v-=rc_sze;
    }
}
inline void makeroot(node *x){
    access(x);splay(x);x->rev();
}
inline void link(node *x,node *y){
    makeroot(x);makeroot(y);x->fa=y;
    y->sze_v+=(x->sze_v+x->sze_t);
}
inline void query(node *x,node *y){
    makeroot(x);int sze1=x->sze_t+x->sze_v;
    access(y);int sze2=y->sze_v+1;
    W(1ll*(sze1-sze2)*sze2);putchar('\n');
}
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=m;i++){
        static char ch[2];
        scanf("%s",ch+1);
        (ch[1]=='A')?(link(pool+rd(),pool+rd())):(query(pool+rd(),pool+rd()));
    }
}

线段树合并一样可以处理,考虑先把原树的关系处理出来,对于当前联通快,按照 dfs 序查询两颗子树就好了。

#include<bits/stdc++.h>
using namespace std;
struct IO
{
    streambuf *ib,*ob;
    int buf[50];
    inline void init()
    {
        ios::sync_with_stdio(false);
        cin.tie(NULL);cout.tie(NULL);
        ib=cin.rdbuf();ob=cout.rdbuf();
    }
    inline int read()
    {
        char ch=ib->sbumpc();int i=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=ib->sbumpc();}
        while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=ib->sbumpc();}
        return i*f;
    }
    inline void W(long long x)
    {
        if(!x){ob->sputc('0');ob->sputc('\n');return;}
        if(x<0){ob->sputc('-');x=-x;}
        while(x){buf[++buf[0]]=x%10;x/=10;}
        while(buf[0]){ob->sputc(buf[buf[0]--]+'0');}
        ob->sputc('\n');
    }
}io;

const int Maxn=1e5+50;
int n,m,ecnt,rt[Maxn],dfn[Maxn],dep[Maxn],anc[Maxn],ind,out[Maxn],tot;
int sze[Maxn*20],lc[Maxn*20],rc[Maxn*20];
struct Q
{
    int k,x,y;
}q[Maxn];
vector<int>edge[Maxn];
inline void dfs(int now,int f)
{
    dfn[now]=++ind;dep[now]=dep[f]+1;
    for(int e=edge[now].size()-1;e>=0;e--)
    {
        int v=edge[now][e];
        if(v==f)continue;
        dfs(v,now);
    }
    out[now]=ind;
}
inline void update(int now)
{
    sze[now]=sze[lc[now]]+sze[rc[now]];
}
inline int getf(int now)
{
    return (anc[now]==now)?(now):(anc[now]=getf(anc[now]));
}
inline void insert(int now,int l,int r,int pos)
{
    if(l==r)
    {
        sze[now]=1;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)insert(lc[now]=++tot,l,mid,pos);
    else insert(rc[now]=++tot,mid+1,r,pos);
    update(now);
}
inline int merge(int x,int y)
{
    if(!y)return x;
    if(!x)return y;
    sze[x]+=sze[y];
    lc[x]=merge(lc[x],lc[y]);
    rc[x]=merge(rc[x],rc[y]);
    return x;
}
inline int query(int now,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)return sze[now];
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid&&lc[now])res+=query(lc[now],l,mid,L,R);
    if(R>mid&&rc[now])res+=query(rc[now],mid+1,r,L,R);
    return res;
}
int main()
{
    io.init();n=io.read(),m=io.read();
    for(int i=1;i<=n;i++)anc[i]=i;
    for(int i=1;i<=m;i++)
    {
        static char ch[3];
        cin>>(ch+1);
        q[i].k=((ch[1]=='A')?1:2),q[i].x=io.read(),q[i].y=io.read();
        if(q[i].k==1)
        {
            edge[q[i].x].push_back(q[i].y);
            edge[q[i].y].push_back(q[i].x);
        }
    }
    for(int i=1;i<=n;i++)if(!dfn[i])dfs(i,0);
    for(int i=1;i<=n;i++)
    {
        rt[i]=++tot;
        insert(rt[i],1,n,dfn[i]);
    }
    for(int i=1;i<=m;i++)
    {
        if(dep[q[i].x]>dep[q[i].y])swap(q[i].x,q[i].y);
        if(q[i].k==1)
        {
            rt[getf(q[i].x)]=merge(rt[getf(q[i].x)],rt[getf(q[i].y)]);
            anc[getf(q[i].y)]=anc[q[i].x];
        }
        else
        {
            int k=getf(q[i].y);
            int v=query(rt[k],1,n,dfn[q[i].y],out[q[i].y]);
            io.W(1ll*(sze[rt[k]]-v)*v);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值