BZOJ P4551 LOJ P2054 [TJOI2016][HEOI2016] 树【并查集】

题目分析:

总之这是一道很水的题,没有多么难,也就是普通的逆向思维一发就好了。

首先这道题是很显然的一道并查集可做的题(据说可以用一些高级的数据结构),我们正向考虑每个点所在的集合 F [ ] 所代表的含义,很显然的一个想法就是每个点所在的集合的关键字都是它最近的打了标记的祖先,但是这样设定集合含义的话是有问题的,因为当我们对一个添加标记的时候,有可能会导致以这个点为根节点的所有节点的 F [ ] 都发生改变,而显然通过遍历子树的方法来处理就不优秀了。

所以接下来我们考虑一下反向处理。

对于样例来说:第二个 Q 2 Q 5 Q 3 所在的树中打了标记的点是 1 号点和 C 2 ,也就当前的树已经标记好了所有需要标记的点,而对于第一个 Q 1 ,其实就相当于在标记好了所有的点的树中删去 C 2

对于所有的操作我们先记录并处理需要标记的,如果一个点被标记过了,就将 F [ I ] = I ,没有标记的话就将 F [ I ] = F a [ I ] ,然后倒着进行操作,如果遇到 Q 也就是询问,我们只需要 F i n d ( X ) ,如果是 C 修改的话,就相当于删除当前的标记,由于一个点可能被标记多次,当它的标记全部被删去的时候,就如初始化一样,将它的 F [ ] 指向父亲节点: F [ I ] = F a [ I ]

关键代码:

int main(){
    LL I,J,K;
    N=Read(),M=Read();
    ......
    for(I=1;I<=M;I++){
        scanf("%s",CH[I]);D[I]=Read();
        if(CH[I][0]=='C'){
            Mark[D[I]]++;
        }
    }
    ......
    for(I=M;I>=1;I--){
        if(CH[I][0]=='Q'){
            Ans[I]=Find(D[I]);
        } else {
            Mark[D[I]]--;
            if(Mark[D[I]]==0){
                F[D[I]]=Fa[D[I]];
            }
        }
    }
    for(I=1;I<=M;I++){
        if(CH[I][0]=='Q'){
            Write(Ans[I]),putchar('\n');
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/80777038