[BZOJ 4551][Tjoi2016&Heoi2016]树(并查集)

Description

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

Solution

还是非常巧妙的思维

离线,先记录每个点的标记数,倒序处理询问,遇到打标记就把标记数减一,对于标记数为0的点就用并查集与它的父节点合并

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<vector>
#define MAXN 100005
using namespace std;
int n,q,head[MAXN],cnt=0,f[MAXN],father[MAXN],num[MAXN],sign[MAXN];
char opt[MAXN];
vector<int>v;
struct Node
{
    int next,to;
}Edges[MAXN*2];
void addedge(int u,int v)
{
    Edges[++cnt].next=head[u];
    head[u]=cnt;
    Edges[cnt].to=v;
}
int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';c=getchar();
    }
    return x*f;
}
int find(int x)
{
    if(father[x]==x)return x;
    return father[x]=find(father[x]);
}
void dfs(int u)
{
    if(!sign[u])father[u]=find(f[u]); 
    for(int i=head[u];~i;i=Edges[i].next)
    {
        int v=Edges[i].to;
        if(v==f[u])continue;
        f[v]=u;
        dfs(v);
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    n=read(),q=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read();
        addedge(u,v);
        addedge(v,u);
        father[i]=i;
    }
    father[n]=n,sign[1]++;
    for(int i=1;i<=q;i++)
    {
        opt[i]=getchar();
        while(opt[i]!='Q'&&opt[i]!='C')opt[i]=getchar();
        num[i]=read();
        if(opt[i]=='C')sign[num[i]]++;
    }
    dfs(1);
    for(int i=q;i>0;i--)
    {
        if(opt[i]=='C')
        {
            sign[num[i]]--;
            if(!sign[num[i]])
            father[num[i]]=find(f[num[i]]);
        }
        else
        v.push_back(find(num[i]));
    }
    for(int i=v.size()-1;i>=0;i--)
    printf("%d
",v[i]);
    return 0;
} 
原文地址:https://www.cnblogs.com/Zars19/p/6872287.html