bzoj2049: [Sdoi2008]Cave 洞穴勘测(lct)

来源:互联网 发布:linux强制退出vi 编辑:程序博客网 时间:2024/05/17 21:46

Description

辉辉热衷于洞穴勘测。某天,他按照地图来到了一片被标记为JSZX的洞穴群地区。经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴。假如两个洞穴可以通过一条或者多条通道按一定顺序连接起来,那么这两个洞穴就是连通的,按顺序连接在一起的这些通道则被称之为这两个洞穴之间的一条路径。洞穴都十分坚固无法破坏,然而通道不太稳定,时常因为外界影响而发生改变,比如,根据有关仪器的监测结果,123号洞穴和127号洞穴之间有时会出现一条通道,有时这条通道又会因为某种稀奇古怪的原因被毁。辉辉有一台监测仪器可以实时将通道的每一次改变状况在辉辉手边的终端机上显示:如果监测到洞穴u和洞穴v之间出现了一条通道,终端机上会显示一条指令 Connect u v 如果监测到洞穴u和洞穴v之间的通道被毁,终端机上会显示一条指令 Destroy u v 经过长期的艰苦卓绝的手工推算,辉辉发现一个奇怪的现象:无论通道怎么改变,任意时刻任意两个洞穴之间至多只有一条路径。因而,辉辉坚信这是由于某种本质规律的支配导致的。因而,辉辉更加夜以继日地坚守在终端机之前,试图通过通道的改变情况来研究这条本质规律。然而,终于有一天,辉辉在堆积成山的演算纸中崩溃了……他把终端机往地面一砸(终端机也足够坚固无法破坏),转而求助于你,说道:“你老兄把这程序写写吧”。辉辉希望能随时通过终端机发出指令 Query u v,向监测仪询问此时洞穴u和洞穴v是否连通。现在你要为他编写程序回答每一次询问。已知在第一条指令显示之前,JSZX洞穴群中没有任何通道存在。

Input

第一行为两个正整数n和m,分别表示洞穴的个数和终端机上出现过的指令的个数。以下m行,依次表示终端机上出现的各条指令。每行开头是一个表示指令种类的字符串s(”Connect”、”Destroy”或者”Query”,区分大小写),之后有两个整数u和v (1≤u, v≤n且u≠v) 分别表示两个洞穴的编号。

Output

对每个Query指令,输出洞穴u和洞穴v是否互相连通:是输出”Yes”,否则输出”No”。(不含双引号)

Sample Input

样例输入1 cave.in
200 5
Query 123 127
Connect 123 127
Query 123 127
Destroy 127 123
Query 123 127

样例输入2 cave.in
3 5
Connect 1 2
Connect 3 1
Query 2 3
Destroy 1 3
Query 2 3

Sample Output

样例输出1 cave.out
No
Yes
No

样例输出2 cave.out
Yes
No

题意:给n个独立的点,有m次操作,每次操作可以连接两点、断开两点,或查询两点是否联通。

题解:动态树。
用lct维护森林。

基本操作

用Splay 维护实路径:由于每条实路径是由首尾相连的实边构成的,因此实路径上任意
两个点都是祖先与子孙的关系。换句话说,如果用深度作为关键字给结点排序,那么我们将
得到一个唯一的有序结点序列。基于这个思想,我们将这个结点序列用平衡树维护,平衡树
中每个结点的左子树中结点在实路径中的深度都小于该结点,右子树中的都大于该结点,因
此平衡树的最左结点对应该路径的头部,最右结点对应该路径的尾部。

几个基本操作:

  1. Access(x):
    以x 为起点,一直到根节点,构造出一条链。
    这是针对某个结点x 的操作,该操作将x 到根结点的路径上的所有边都变为实边,当然,
    为了保持实边、虚边划分的性质,一部分原来的实边也要相应变为虚边。注意该操作会将x
    下方的实边变为虚边。该操作的步骤如下:
    (1) 如果结点x 不是其所在实路径的尾部,即x 有子结点与之用实边相连,那么需要“断
    开”这条边(断开并不是将这条边删除,而只是将其转变为虚边)。方法是首先将结点x
    用Splay 操作旋转到所在平衡树的根结点,然后x 肯定有右子树,故将x 与x 的右子树分离,
    同时将x 的右子树的Path_Parent 设置为x。
    (2) 如果结点x 所在的平衡树包含根结点,那么该过程结束;否则,转步骤(3);
    (3) 设y 为x 所在平衡树的Path_Parent。将y 用Splay 操作旋转到其所属平衡树的根结
    点,并且用x 所在的平衡树替换y 的右子树,这样就实现了实路径的向上延伸。当然到这里
    还没有结束,我们需要分离原来y 的右子树,y 原来的右孩子记为P。此时P 的Path_Parent
    就为y 了,然后继续转步骤(2)。
    实际实现时,我们并不需要显示维护出Path_Parent,我们可以稍微更改Splay 中的实现,
    即我们考虑Splay 中根节点的fa,我们并不需要将其置为0,而是将其置为Path_Parent,这
    样每个点的父亲,要么是实路径上的点,要么是它所处实路径的Path_Parent,特别地,这
    棵树的根节点的Path_Parent 为0。这样的话一个点,它的fa 的左右儿子可能都不为它,因
    此判断根节点的条件也需要稍微修改一下。

  2. FindRoot(x):
    找到结点x 所在树的根结点。
    有了Access(x)操作,寻找树根的操作就比较简单了。我们只要对结点x 执行Access(x),
    便使得x 与要找的根结点在同一棵平衡树中了。然后,要找的根结点一定是实路径的头部,
    即平衡树中的最左结点。

  3. MakeRoot(x):
    使结点x 成为所在树的根结点。
    该操作等价于将从结点x 到当前根结点的路径上的所有树边的方向取反。
    首先执行Access(x),我们便已经将该路径取出了。由于路径是用平衡树维护,所以需要
    执行的是平衡树的区间翻转操作,我们可以借鉴线段树的打标记(懒操作)思想,给平衡树
    结点也用打标记的方式来完成反序操作。这里由于是对整棵平衡树反序,所以标记应该打在
    平衡树的根结点处。打标记后注意在其他操作的地方(例如旋转后)及时下传以及更新。

  4. Link(x,y):使得节点x 成为y 的孩子节点,也就是连一条(x,y)的边。
    这只需要执行一次MakeRoot(x)操作,随后将x 的fa 置为y 即可。

  5. Cut(x,y):删除树中(x,y)的这条边。
    该操作执行时,首先将x 置为有根树的根结点,从而保证y 一定是x 的子结点。再对y
    执行Access 操作,我们便将x 和y 合并到同一平衡树中了。此时对y 执行Splay 操作使其
    成为所在平衡树的根结点,同时分离y 和y 的左子树,便完成了边的删除操作。(此时若树
    中存在(x,y)这条边,则y 的左子树一定只有x 这一个节点)。

#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<algorithm>#include<cmath>using namespace std;const int Maxn=2e5+50;typedef long long ll;inline int read(){    char ch=getchar();int i=0,f=1;    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while(ch>='0'&&ch<='9'){i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}    return i*f; }struct node{    node *lc,*rc,*fa;    int tag;    node():lc(NULL),rc(NULL),fa(NULL),tag(0){}}*tr[Maxn];inline bool which(node *x){    return x->fa->lc==x;}inline bool isroot(node *x){    return !x->fa||(x->fa->lc!=x&&x->fa->rc!=x);}inline void rotate(node *x){    node *y=x->fa,*z=y->fa;    if(z&&!isroot(y))    {        if(y==z->lc)z->lc=x;        else z->rc=x;    }    y->fa=x,x->fa=z;    if(x==y->lc)    {        node *b=x->rc;        if(b)b->fa=y;        y->lc=b;        x->rc=y;    }    else    {        node *b=x->lc;        if(b)b->fa=y;        y->rc=b;        x->lc=y;    }}inline void downdate(node *x){    if(x->tag)    {        swap(x->lc,x->rc);        if(x->lc)x->lc->tag^=1;        if(x->rc)x->rc->tag^=1;        x->tag=0;    }}inline void splay(node *x){    static node *que[Maxn];    int qn=0;    que[qn=1]=x;    for(node *y=x;!isroot(y);y=y->fa)que[++qn]=y->fa;    for(int i=qn;i>=1;i--)downdate(que[i]);    while(!isroot(x))    {        node *y=x->fa;        if(!isroot(y))        {            if(which(x)^which(y))rotate(x);            else rotate(y);        }        rotate(x);    }}inline void access(node *x){    for(node *y=NULL;x;y=x,x=x->fa)    {        splay(x);x->rc=y;        if(y)y->fa=x;    }}inline void makeroot(node *x){    access(x);    splay(x);    x->tag^=1;}inline node *findroot(node *x){    access(x);    splay(x);    while(x->lc)x=x->lc;    return x;}inline void cut(node *x,node *y){    makeroot(x);    access(y);    splay(y);    x->fa=0;    y->lc=NULL;}inline void link(node *x,node *y){    makeroot(x);    x->fa=y;}int main(){    int n=read(),Q=read();      for(int i=1;i<=n;i++)tr[i]=new node();    while(Q--)    {        static char ch[10];        scanf("%s",ch+1);        if(ch[1]=='C')        {            int x=read(),y=read();            link(tr[x],tr[y]);        }        else if(ch[1]=='Q')        {            int x=read(),y=read();            makeroot(tr[y]);            node *tmp=findroot(tr[x]);            if(tmp==tr[y])puts("Yes");            else puts("No");        }        else        {            int x=read(),y=read();            cut(tr[x],tr[y]);        }    }}
0 0