bzoj4551: [Tjoi2016&Heoi2016]树 并查集

bzoj4551: [Tjoi2016&Heoi2016]树

Description

在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下
两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个
结点,可以打多次标记。)2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖
先)你能帮帮他吗?

Input

输入第一行两个正整数N和Q分别表示节点个数和操作次数接下来N-1行,每行两个正整数u,v(1≤u,v≤n)表示u到v
有一条有向边接下来Q行,形如“opernum”oper为“C”时表示这是一个标记操作,oper为“Q”时表示这是一个询
问操作对于每次询问操作,1 ≤ N, Q ≤ 100000。

Output

输出一个正整数,表示结果

Sample Input

5 5
1 2
1 3
2 4
2 5
Q 2
C 2
Q 2
Q 5
Q 3

Sample Output

1
2
2
1

HINT

新加数据9组(By HFLSyzx ),未重测–2016.8.2

分析

如果是线段树+树链剖分这题瞬秒,打标记就是把当前节点dfs序加入线段树里,查询最近祖先其实就是查询到根路径最大值,当然对偶一下也一样。
但这道题有个巧妙的方法,用并查集合并。
倒着做,变成删除标记,把所有未标记的节点和父亲合并,删掉一个标记就吧节点和父亲合并。
很有道理是不是。
贼好写

代码

#include<cstdio>
const int N = 1e5 + 10;
int ri() {
    char ch = getchar(); int x = 0; for(;ch < '0' || ch > '9'; ch = getchar()) ;
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch; return x;
}
int rc() {char ch = getchar(); for(;ch != 'Q' && ch != 'C'; ch = getchar()) ; return ch == 'C';}
bool p[N]; int g[N], f[N], a[N], x[N], r[N], pr[N], nx[N << 1], to[N << 1], tp;
void add(int u, int v) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp;}
void adds(int u, int v) {add(u, v); add(v, u);}
int fi(int x) {return !g[x] ? x : g[x] = fi(g[x]);}
void Dfs(int u, int F) {for(int i = pr[u]; i; i = nx[i]) if(to[i] != F) Dfs(to[i], f[to[i]] = u);}
int main() {
    int n = ri(), m = ri();
    for(int i = 1;i < n; ++i) adds(ri(), ri()); Dfs(r[1] = 1, 0);
    for(int i = 1;i <= m; ++i) {
        p[i] = rc(); x[i] = ri();
        r[x[i]] += p[i];
    }
    for(int i = 1;i <= n; ++i) if(!r[i]) g[fi(i)] = fi(f[i]);
    for(int i = m; i; --i) 
        if(p[i]) {if(!--r[x[i]]) g[fi(x[i])] = fi(f[x[i]]);}
        else a[i] = fi(x[i]);
    for(int i = 1;i <= m; ++i) if(!p[i]) printf("%d\n", a[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/80784883