[bzoj3510]首都

题目大意

一开始有n个结点,没有边。
有三种操作:将两个结点间连一条边,并且保证两个结点属不同联通块。询问一个联通块中所有点到其距离和最小的点。询问所有联通块中所有点到其距离和最小的点的异或和。

启发式合并

显然是一片森林,要求维护重心。
可以用启发式合并的思路,把小的合到大的里面,然后调整原本大的树里的重心,显然这个重心只会朝着小树方向调整,而且最多移动小树大小步。
然后需要用LCT来维护森林,并且要动态维护子树大小,还要兹瓷换根。怎么维护呢?
设siz[x]表示x的所有轻儿子子树大小和,那么想要获得x子树大小可以access(x)然后结果就是siz[x]+1。
spaly中维护size表示子树大小以及sum表示子树siz的和。
那么只需要在虚实交换的时候改变siz就行了。
也就是y是x一个儿子,y对x的影响是sum[y]+size[y]的(注意得保证y是其所在splay的根节点)
代码如下:

void real_empty(int x){
    splay(x,0);
    if (!tree[x][1]) return;
    int y=suc(x);
    splay(y,x);
    siz[x]+=sum[y]+size[y];
    pp[y]=x;
    tree[x][1]=0;
    father[y]=0;
    update(x);
}
void empty_real(int x,int y){
    splay(x,0);
    splay(y,0);
    tree[x][1]=y;
    father[y]=x;
    pp[y]=0;
    siz[x]-=sum[y]+size[y];
    update(x);
}

那么因为siz不受实边影响,所以可以兹瓷换根操作。
实现方面:
1、每棵树用并查集维护,保存大小与重心。
2、注意考虑一颗树两个重心的情况!
3、将一条虚边变为实边必须保证y是x的儿子,所以access不能像以前一样大,每次要把x调整成splay中深度最小点,用kth操作解决。
4、因为换根所需要的翻转标记,所以求前驱、后继以及深度最小点均不能直接上(除非你顺便下传标记),最好都用kth操作解决,kth里注意下传标记。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int fa[maxn],big[maxn],zx[maxn];
int father[maxn],siz[maxn],size[maxn],sum[maxn],pp[maxn],tree[maxn][2],sta[maxn]; 
int tiaoshi[maxn];
bool bz[maxn];
int i,j,k,l,t,n,m,ans,top;
char ch;
int pd(int x){
    return tree[father[x]][1]==x;
}
void update(int x){
    size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
    sum[x]=sum[tree[x][0]]+sum[tree[x][1]]+siz[x];
}
void rotate(int x){
    int y=father[x],z=pd(x);
    father[x]=father[y];
    if (father[y]) tree[father[y]][pd(y)]=x;
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) father[tree[x][1-z]]=y;
    tree[x][1-z]=y;
    father[y]=x;
    update(y);
    update(x);
    if (pp[y]) pp[x]=pp[y],pp[y]=0;
}
void mark(int x){
    if (!x) return;
    bz[x]^=1;
    swap(tree[x][0],tree[x][1]);
}
void down(int x){
    if (bz[x]){
        mark(tree[x][0]);
        mark(tree[x][1]);
        bz[x]=0;
    }
}
void remove(int x,int y){
    top=0;
    while (x!=y){
        sta[++top]=x;
        x=father[x];
    }
    while (top){
        down(sta[top]);
        top--;
    }
}
void splay(int x,int y){
    remove(x,y);
    while (father[x]!=y){
        if (father[father[x]]!=y)
            if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
        rotate(x);
    }
}
int getfa(int x){
    return fa[x]?fa[x]=getfa(fa[x]):x;
}
int kth(int x,int y){
    down(x);
    if (size[tree[x][0]]+1==y) return x;
    else if (size[tree[x][0]]>=y) return kth(tree[x][0],y);
    else return kth(tree[x][1],y-size[tree[x][0]]-1);
}
int pre(int x){
    splay(x,0);
    return kth(tree[x][0],size[tree[x][0]]);
}
int suc(int x){
    splay(x,0);
    return kth(tree[x][1],1);
}
void real_empty(int x){
    splay(x,0);
    if (!tree[x][1]) return;
    int y=suc(x);
    splay(y,x);
    siz[x]+=sum[y]+size[y];
    pp[y]=x;
    tree[x][1]=0;
    father[y]=0;
    update(x);
}
void empty_real(int x,int y){
    splay(x,0);
    splay(y,0);
    tree[x][1]=y;
    father[y]=x;
    pp[y]=0;
    siz[x]-=sum[y]+size[y];
    update(x);
}
void access(int x){
    int y;
    real_empty(x);
    splay(x,0);
    x=kth(x,1);
    splay(x,0);
    while (pp[x]){
        y=pp[x];
        real_empty(y);
        empty_real(y,x);
        splay(x,0);
        x=kth(x,1);
        splay(x,0);
    }
}
void makeroot(int x){
    access(x);
    splay(x,0);
    mark(x);
}
void merge(int x,int y){
    int xx=getfa(x),yy=getfa(y);
    ans^=zx[xx]^zx[yy];
    if (big[xx]<big[yy]||big[xx]==big[yy]&&x>y){
        swap(x,y);
        swap(xx,yy);
    }
    makeroot(x);
    makeroot(y);
    splay(x,0);
    splay(y,0);
    siz[x]+=sum[y]+size[y];
    update(x);
    pp[y]=x;
    big[xx]+=big[yy];
    fa[yy]=xx;
    zx[yy]=0;
    int j=zx[xx],k;
    while (j!=x){
        access(j);
        k=pre(j);
        if (big[xx]%2==1&&big[xx]-siz[j]-1<=big[xx]/2||big[xx]%2==0&&big[xx]-siz[j]-1<big[xx]/2||big[xx]%2==0&&big[xx]-siz[j]-1==big[xx]/2&&j<k) break;
        j=k;
    }
    zx[xx]=j;
    ans^=j;
}
char get(){
    char ch=getchar();
    while (ch!='A'&&ch!='Q'&&ch!='X') ch=getchar();
    return ch;
}
int main(){
    freopen("cap.in","r",stdin);freopen("cap.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n) ans^=(zx[i]=i),big[i]=size[i]=1;
    fo(i,1,m){
        ch=get();
        if (ch=='A'){
            scanf("%d%d",&j,&k);
            merge(j,k);
        }
        else if (ch=='Q'){
            scanf("%d",&j);
            j=getfa(j);
            printf("%d\n",zx[j]);
        }
        else printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值