BZOJ3510: 首都
额,这个在BZOJ上式权限题,本蒟蒻表示是个穷银,买不起权限号,这里贴上洛谷的题面:
还是洛谷吼!
题目描述
在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:
A x y
:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。Q x
:询问当前编号为x的城市所在国家的首都。Xor
:询问当前所有国家首都编号的异或和。
输入输出格式
输入格式:
第一行是整数N,M,表示城市数和需要处理的信息数。 接下来每行是一个信息,格式如题目描述(A、Q、Xor中的某一种)。
输出格式:
输出包含若干行,为处理Q和Xor信息的结果。
输入输出样例
说明
对于100%的数据,2<=N<=100000,1<=M<=200000。
题解Here!
这个题就动态维护树的重心就好辣。
动态维护树的形态,当然拿出$LCT$!
题目中说到国家的首都会选在某个使得其他城市到它距离之和最小的城市,那不就是树的重心了嘛。
然后树的重心有很多性质,具体可以看这位大佬的博客:链接。
维护子树信息是不会少的。
在此基础上,网上大多数解法都是启发式合并。
即利用了“以重心为根的子树大小不超过原树的一半”这个性质,每次合并两颗树的时候,小的往大的上面合并,而且是一个点一个点地$link$上去。
每次$link$完检查一下这个子树,如果超过了当前整个树大小的一半,就把重心向当前点移动一下。
不得不说复杂度爆棚啊。。。
合并次数上限是$O(n\log_2n)$,每次更新重心是$O(\log_2n)$,总复杂度是$O(n\log_2^2n)$的。
然后我们就想再优化一下算法。
复杂度的瓶颈就在于一个一个$link$,能不能考虑直接连接两颗树对重心的影响呢?
答案是肯定的。
又要用到一个性质——连接两颗树后,新的重心一定在原来的两个重心的路径上。
这个性质吼啊!
那就可以直接$link$,然后把链提出来去找了。
$BUT$!我们当然不能把整条链全找遍了,毕竟重心的性质那么多,也要好好利用嘛。
做法就是这样:
类似树上二分,我们需要不断逼近树的重心的位置。
记下$lsum$表示当前链中搜索区间左端点以左的子树大小,$rsum$表示右端点以右的。
$LCT$怎么维护子树信息?
设虚子树信息总和用数组$v$表示,原树信息总和用$s$表示。
此处$a[x].v$只包含$x$所有虚子树(通过轻边指向$x$)的信息总和。
而$a[x].s$实际上是在$LCT$中的所有儿子的信息总和(包括辅助树$Splay$中相对的左右儿子的总和与被轻边所指的绝对的虚子树的总和)。
具体的看代码吧,一句话说不清的。。。
$x$的整个子树就表示了当前搜索区间,在中序遍历中$x$把搜索区间分成了左右两块(在$Splay$中对应$x$的左子树和右子树)。
如果$x$左子树的$s$加上$lsum$和$x$右子树的$s$加上$rsum$都不超过新树总大小的一半,那么$x$当然就是重心啦!
当然,如果总大小是奇数,重心只会有一个,那就找到了。
否则,因为必须编号最小,所以还要继续找下去。
当我们没有确定答案时,还要继续找下去,那么就要跳儿子了。
$x$把整个链分成了左右两个部分,而重心显然会在大小更大的一部分中,这个也应该好证明。
如果$x$左子树的$s$加上$lsum$小于$x$右子树的$s$加上$rsum$,那就跳右儿子继续找。
这时候当前搜索区间减小了,搜索区间以外的部分增大了,$lsum$应该加上$v+1$。
反之亦然。
如果跳进了空儿子,那肯定所有情况都考虑完了,直接结束查找。
当然,重心找到了就还是要伸展一下,保证复杂度。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 100010 #define MAX 999999999 using namespace std; int n,m,sum=0; int top=0,fa[MAXN],stack[MAXN]; struct Link_Cut_Tree{ int son[2]; int f,v,s,flag; }a[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline bool isroot(int rt){ return a[a[rt].f].son[0]!=rt&&a[a[rt].f].son[1]!=rt; } inline void pushup(int rt){ if(!rt)return; a[rt].s=a[rt].v+a[a[rt].son[0]].s+a[a[rt].son[1]].s+1; } inline void pushdown(int rt){ if(!rt||!a[rt].flag)return; a[a[rt].son[0]].flag^=1;a[a[rt].son[1]].flag^=1;a[rt].flag^=1; swap(a[rt].son[0],a[rt].son[1]); } inline void turn(int rt){ int x=a[rt].f,y=a[x].f,k=a[x].son[0]==rt?1:0; if(!isroot(x)){ if(a[y].son[0]==x)a[y].son[0]=rt; else a[y].son[1]=rt; } a[rt].f=y;a[x].f=rt;a[a[rt].son[k]].f=x; a[x].son[!k]=a[rt].son[k];a[rt].son[k]=x; pushup(x);pushup(rt); } void splay(int rt){ top=0; stack[++top]=rt; for(int i=rt;!isroot(i);i=a[i].f)stack[++top]=a[i].f; while(top)pushdown(stack[top--]); while(!isroot(rt)){ int x=a[rt].f,y=a[x].f; if(!isroot(x)){ if((a[x].son[0]==rt)^(a[y].son[0]==x))turn(rt); else turn(x); } turn(rt); } } void access(int rt){ for(int i=0;rt;i=rt,rt=a[rt].f){ splay(rt); a[rt].v+=a[a[rt].son[1]].s; a[rt].son[1]=i; a[rt].v-=a[a[rt].son[1]].s; pushup(rt); } } inline void makeroot(int rt){access(rt);splay(rt);a[rt].flag^=1;} int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} inline void split(int x,int y){makeroot(x);access(y);splay(y);} inline void link(int x,int y){split(x,y);a[x].f=y;a[y].v+=a[x].s;pushup(y);} int change(int x){ int now=MAX,flag=a[x].s&1,s=a[x].s>>1,nowl,nowr,lsum=0,rsum=0; while(x){ pushdown(x); int lson=a[x].son[0],rson=a[x].son[1]; nowl=a[lson].s+lsum;nowr=a[rson].s+rsum; if(nowl<=s&&nowr<=s){ if(flag){ now=x; break; } else now=min(now,x); } if(nowl<nowr){ lsum+=a[lson].s+a[x].v+1; x=rson; } else{ rsum+=a[rson].s+a[x].v+1; x=lson; } } splay(now); return now; } void work(){ char ch[5]; int x,y,now; while(m--){ scanf("%s",ch); if(ch[0]=='A'){ x=read();y=read(); link(x,y); x=find(x);y=find(y); split(x,y); now=change(y); sum=sum^x^y^now; fa[x]=fa[y]=fa[now]=now; } else if(ch[0]=='Q'){ x=read();x=find(x); printf("%d\n",x); } else printf("%d\n",sum); } } void init(){ n=read();m=read(); for(int i=1;i<=n;i++){ a[i].s=1; fa[i]=i; sum^=i; } } int main(){ init(); work(); return 0; }