首都 洛谷4299 LCT

题目链接

这题是bzoj权限题,我没权限号,所以没法交,只好在洛谷上做。
题解:
我们发现,对于A操作,支持连接,首先就会想到LCT,然后还可能是离线并查集之类的东西,但是我们发现似乎并查集并不容易维护这个要求是树的重心的首都,所以考虑用LCT。网上大多数的做法是启发式合并,每次从size小的树找一个点合并到size大的树上,这样每次合并重心最多移动一次,这样复杂度应该是nlog^2n(n*logn*logn)。但是我在网上学到了一个复杂度更优的做法。我们发现,上面的做法的复杂度瓶颈在于启发式合并时一个一个地加点,那么我们考虑是否可以直接把两棵树合并呢?答案是可以的。根据重心的一些性质,我们可以发现合并前后,新的重心应该会出现在两棵树连通后原来的两个首都之间的路径上。那么我们 合并之后用LCT把这条路径提取出来,只需要在这条路径上找即可。然后写法上是一种类似二分的写法,也是一个要维护虚子树大小的题,维护方法与大融合那道题相同。
再看Q操作,相当于找一个连通块的根,这个LCT和并查集都可以,但是显然用并查集维护会常数小。
对于Xor操作,我们只需要一开始预处理出n个数的异或和,每连接两个点,就把它们原来的首都的编号异或掉(同一个数被异或两边相当于对答案没有贡献,一开始异或过一次,这里再异或一次就相当于去掉了),再异或上新的首都。
下面是代码,重点就是update函数。
 
 
#include <bits/stdc++.h>
using namespace std;

int n,m,f[100010],c[100010][2],fa[100010],rev[100010],st[100010];
int res,s[100010],si[100010];
inline int getr(int x)
{
    if(x==fa[x])
    return x;
    else
    {
        fa[x]=getr(fa[x]);
        return fa[x];
    }
}
inline void pushup(int x)
{
    s[x]=s[c[x][0]]+s[c[x][1]]+si[x]+1;
}
inline void pushdown(int x)
{
    if(rev[x])
    {
        swap(c[x][0],c[x][1]);
        rev[c[x][0]]^=1;
        rev[c[x][1]]^=1;
        rev[x]=0;
    }
}
inline int nroot(int x)
{
    return c[f[x]][0]==x||c[f[x]][1]==x;
}
inline void rotate(int x)
{
    int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
    if(nroot(y))
    c[z][c[z][1]==y]=x;
    c[x][!k]=y;
    c[y][k]=w;
    if(w)
    f[w]=y;
    f[y]=x;
    f[x]=z;
    pushup(y);  
}
inline void splay(int x)
{
    int y=x,z=0;
    st[++z]=y;
    while(nroot(y))
    {
        y=f[y];
        st[++z]=y;
    }
    while(z)
    pushdown(st[z--]);
    while(nroot(x))
    {
        y=f[x],z=f[y];
        if(nroot(y))
        {
            if(c[z][0]==y ^ c[y][0]==x)
            rotate(x);
            else
            rotate(y);
        }
        rotate(x);
    }
    pushup(x);
}
inline void access(int x)
{
    int y=0;
    while(x!=0)
    {
        splay(x);
        si[x]+=s[c[x][1]]-s[y];
        c[x][1]=y;	
        y=x;
        x=f[x];
    }
}
inline void makeroot(int x)
{
    access(x);
    splay(x);
    rev[x]^=1;
}
inline void split(int x,int y)
{
    makeroot(x);
    access(y);
    splay(y);
}
inline void link(int x,int y)
{
    makeroot(x);
    access(y);
    splay(y);
    f[x]=y;
    si[y]+=s[x];
    pushup(y);
}
inline int update(int x)
{
    int l,r,ji=s[x]&1,sum=s[x]>>1,lsum=0,rsum=0,nowl,nowr,now=2e9;
    while(x)
    {
        pushdown(x);//注意pushdown
        l=c[x][0];
        r=c[x][1];
        nowl=s[l]+lsum;
        nowr=s[r]+rsum;
        if(nowl<=sum&&nowr<=sum)
        {
            if(ji)//点数为奇数只有一个重心 
            {
                now=x;
                break;
            }
            else if(now>x)//选编号小的 
            now=x;
        } 
        if(nowl<nowr)
        {
            lsum+=s[l]+si[x]+1;
            x=r;
        }
        else
        {
            rsum+=s[r]+si[x]+1;
            x=l;
        }
    } 
    splay(now);
    return now;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        s[i]=1;
        res^=i;
    }
    char opt[10];
    int x,y,z;
    for(int i=1;i<=m;++i)
    {
        scanf("%s",opt);
        if(opt[0]=='A')
        {
            scanf("%d%d",&x,&y);
            link(x,y);
            x=getr(x);
            y=getr(y);
            split(x,y);
            z=update(y);
            res=res^x^y^z;
            fa[x]=fa[y]=fa[z]=z; 
//去掉原来的两个首都编号,异或上新的首都编号 
        }
        else if(opt[0]=='Q')
        {
            scanf("%d",&x);
            printf("%d\n",getr(x));
        }
        else
        printf("%d\n",res); 
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/80466126
LCT