【Tjoi2016&Heoi2016】树

Description

在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下两种操作:

  1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)

  2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖
    先)你能帮帮他吗?

Solution

这题有许多方法,如树链剖分,dfs序+线段树,并查集……(好像没有了?)

当然这题数据水,暴力可以过(当然最好是用正解)。

树链剖分就是模板题,每次标记就给该点权值修改为dfs序,查询直接往上跳。

dfs+线段树,也就是用dfs序表示子树,然后区间修改即可。

但这里我要介绍并查集的思想:首先存入所有询问,倒着来做,每次去掉标记,相当于将该点所在连通块和该点父亲所在联通块合并,然后查询直接找该点所在连通块的祖先即可。但要注意一个问题,一个点可能被打上多个标记(由于数据随机性),所以要在该点第一次被打上标记的时才合并(简单来说,每次打标记就给该点+1,去标记就给该点-1,当该点为0时才与父亲合并)。

复杂度为 O(n+qα(n))

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
#define N 100001
using namespace std;
int f[N],cnt[N],fa[N];
int b[N][2];
int c[N];
int find(int x)
{
    return !f[x]?x:f[x]=find(f[x]);
}
int main()
{
    int n,q;
    cin>>n>>q;
    fo(i,1,n-1)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        fa[v]=u;
        f[v]=u;
    }
    fo(i,1,q)
    {
        char ch[3];
        int x;
        scanf("%s %d",ch,&x);
        if(ch[0]=='Q') b[i][0]=1;
        else b[i][0]=0,f[x]=0,cnt[x]++;
        b[i][1]=x;
    }
    fd(i,q,1)
    {
        if(b[i][0]) c[i]=find(b[i][1]);
        else
        {
            cnt[b[i][1]]--;
            if(!cnt[b[i][1]])
            {
                int fx=find(b[i][1]),fy=find(fa[b[i][1]]);
                f[fx]=fy;
            }
        }
    }
    fo(i,1,q)
    if(b[i][0]==1) printf("%d\n",c[i]);
}
posted @ 2016-07-12 16:38  sadstone  阅读(39)  评论(0编辑  收藏  举报