[BZOJ3510]首都

luogu

题意

有一个\(n\)个点的森林,一开始一条边也没有,你要支持以下操作:
1、连接两个点。
2、查询一个点所在树(连通块)的重心编号。
3、查询每棵树的重心编号的异或和。

sol

重心有这样的两个性质:
1、以重心为根时每棵子树的大小不超过\(\frac{n}{2}\)
2、两棵树合并后,重心一定在两棵树的重心的连线上。

所以对于操作1,在\(link\)了两个点后,\(split\)原先两棵树的重心连线,这样这条连线上面的点就全部在一棵\(splay\)里面了。然后就在这棵\(splay\)上二分,每次往\(size\)较大的那一棵子树里面跳。
注意重心可能会有两个,所以不能找到一个就直接退出,一定要找到\(splay\)的叶子节点为止。

notice:
1、\(splay\)上二分你要往下跳,就要记得\(pushdown\)\(pushdown\)
2、向下跳完要\(splay\)保证复杂度啊。

code

#include<cstdio>
#include<algorithm>
using namespace std;
int gi()
{
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e5+5;
int n,m,fa[N],ch[2][N],sum[N],sz[N],rev[N],Stack[N],top,p[N],Ans;
char s[10];
bool son(int x){return x==ch[1][fa[x]];}
bool isroot(int x)
{
    return x!=ch[0][fa[x]]&&x!=ch[1][fa[x]];
}
void pushup(int x)
{
    sum[x]=sum[ch[0][x]]+sum[ch[1][x]]+sz[x]+1;
}
void reverse(int x)
{
    swap(ch[0][x],ch[1][x]);rev[x]^=1;
}
void pushdown(int x)
{
    if (!rev[x]) return;rev[x]=0;
    if (ch[0][x]) reverse(ch[0][x]);if (ch[1][x]) reverse(ch[1][x]);
}
void rotate(int x)
{
    int y=fa[x],z=fa[y],c=son(x);
    ch[c][y]=ch[c^1][x];if (ch[c][y]) fa[ch[c][y]]=y;
    fa[x]=z;if (!isroot(y)) ch[son(y)][z]=x;
    ch[c^1][x]=y;fa[y]=x;pushup(y);
}
void splay(int x)
{
    Stack[top=1]=x;
    for (int y=x;!isroot(y);y=fa[y]) Stack[++top]=fa[y];
    while (top) pushdown(Stack[top--]);
    for (int y=fa[x];!isroot(x);rotate(x),y=fa[x])
        if (!isroot(y)) son(x)^son(y)?rotate(x):rotate(y);
    pushup(x);
}
void access(int x)
{
    for (int y=0;x;y=x,x=fa[x])
    {
        splay(x);sz[x]+=sum[ch[1][x]];
        ch[1][x]=y;sz[x]-=sum[ch[1][x]];
        pushup(x);
    }
}
void makeroot(int x)
{
    access(x);splay(x);reverse(x);
}
void split(int x,int y)
{
    makeroot(x);access(y);splay(y);
}
int find(int x){return x==p[x]?x:p[x]=find(p[x]);}
void link(int x,int y)
{
    makeroot(x);makeroot(y);
    fa[x]=y;sz[y]+=sum[x];pushup(y);
    x=find(x);y=find(y);
    split(x,y);
    int gg=n+1,now=y,ls=0,rs=0,size=sum[y]>>1;
    while (now)
    {
        pushdown(now);//往下跳要pushdown啊啊啊啊
        int lsum=ls+sum[ch[0][now]],rsum=rs+sum[ch[1][now]];
        if (lsum<=size&&rsum<=size) gg=min(gg,now);
        if (lsum>rsum) rs+=sum[ch[1][now]]+sz[now]+1,now=ch[0][now];
        else ls+=sum[ch[0][now]]+sz[now]+1,now=ch[1][now];
    }
    splay(gg);
    p[x]=p[y]=p[gg]=gg;Ans^=x^y^gg;
}
int main()
{
    n=gi();m=gi();
    for (int i=1;i<=n;++i) Ans^=i,p[i]=i;
    while (m--)
    {
        scanf("%s",s);int x,y;
        if (s[0]=='X') printf("%d\n",Ans);
        if (s[0]=='Q') x=gi(),printf("%d\n",find(x));
        if (s[0]=='A') x=gi(),y=gi(),link(x,y);
    }
    return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/8747943.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值