题面
题意
给出一棵树,两种操作,给节点打标记(1为根,初始仅1打了标记),询问祖先中距离他最近的打了标记的节点
做法
这题可以用暴力水过去,也可以用树链剖分来做.
但利用并查集可以十分巧妙地解决这题.
首先可以发现随着询问的进行,每点的答案将会被分成越来越多块,而倒过来进行的话则会发现块数将会越来越少直到最后的一块,且每次都是两块并成一块,因此我们可以记录下操作后用并查集维护,若一个点打了标记,则其父节点为其自身,反之为树上的父节点,倒着进行时若一点的标记被消除,则视为其父节点改为树上的父节点
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#define C ch=getchar()
#define N 100100
using namespace std;
int n,m,bb,fa[N],first[N],cnt[N],tmp[N];
char ch;
struct Bn
{
int to,next;
}bn[N<<1];
struct Que
{
char bj;
int u;
}que[N];
stack<int>ans;
inline void add(int u,int v)
{
bb++;
bn[bb].to=v;
bn[bb].next=first[u];
first[u]=bb;
}
void dfs(int now)
{
int p,q;
for(p=first[now];p!=-1;p=bn[p].next)
{
tmp[bn[p].to]=fa[bn[p].to]=now;
dfs(bn[p].to);
}
}
int ff(int u)
{
return u==fa[u]?u:fa[u]=ff(fa[u]);
}
int main()
{
memset(first,-1,sizeof(first));
int i,j,p,q;
cin>>n>>m;
for(i=1;i<n;i++)
{
scanf("%d%d",&p,&q);
add(p,q);
}
fa[1]=cnt[1]=1;
dfs(1);
for(i=1;i<=m;i++)
{
for(C;ch!='Q'&&ch!='C';C);
scanf("%d",&p);
que[i].bj=ch;
que[i].u=p;
if(ch=='C') cnt[p]++,fa[p]=p;
}
for(i=m;i>=1;i--)
{
if(que[i].bj=='C')
{
cnt[que[i].u]--;
if(!cnt[que[i].u]) fa[que[i].u]=tmp[que[i].u];
}
else ans.push(ff(que[i].u));
}
for(;!ans.empty();)
{
printf("%d\n",ans.top());
ans.pop();
}
}