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

题解:

这题的正解仿佛是线段树,但是看了题解,发现并查集也能过。
我们将每个节点的fa定为其父节点,如果打标记,则将其的fa改成自己。
但是正着做的话时间复杂度比较大,我们考虑反着做,要搜索的总步数就会少了很多,这样就能过了。

代码如下:

#include<cstdio>
#include<string>
using namespace std;
const int maxn=100005;
int n,Q,tot,fa[maxn],f[maxn],lnk[maxn],son[2*maxn],nxt[2*maxn],sum[maxn],ans[maxn];
struct dyt{int x,id;} a[maxn];
inline int read(){
    int x=0; char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
    return x;
}
void add(int x,int y){son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot;}
void dfs(int x,int s){
    for (int j=lnk[x];j;j=nxt[j])
    if (son[j]!=s) {f[son[j]]=x; dfs(son[j],x);}
}
int getfa(int x){if (fa[x]!=x) fa[x]=getfa(fa[x]); return fa[x];}
int main(){
    n=read(),Q=read();
    for (int i=1;i<n;i++) {
        int x=read(),y=read(); add(x,y);
    }
    for (int i=1;i<=Q;i++) {
        char ch=getchar(); while (ch!='C'&&ch!='Q') ch=getchar();
        if (ch=='C') a[i].id=1; else a[i].id=2;
        a[i].x=read(); if (a[i].id==1) sum[a[i].x]++;
    }
    f[1]=1; dfs(1,1);
    for (int i=1;i<=n;i++) if (sum[i]) fa[i]=i; else fa[i]=f[i]; fa[1]=1;
    for (int i=Q;i>0;i--) {
        if (a[i].id==1) {
            sum[a[i].x]--;
            if (!sum[a[i].x]) fa[a[i].x]=f[a[i].x];
        } else ans[i]=getfa(a[i].x);
    }
    for (int i=1;i<=Q;i++) if (a[i].id==2) printf("%d\n",ans[i]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dyt_b/article/details/79902124