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
The solution
题解有很多方法,所以在这里只介绍并查集的方法。
将所有的操作倒序处理,利用并查集维护。
对于一个修改操作,相当于将这联通块与它父亲的联通块和并
对于一个查询操作,直接查询所在的块的父亲即可。
至于细节随便乱搞乱搞yy一下即可。
CODE
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define N 200005
using namespace std;
int n,Q,x,y,t[N],last[N],next[N],Dad[N],f[N],mark[N],l=0,ans[N],a[N];
bool bz[N];
char ch[3];
inline int read(int &n)
{
char ch=' ';int q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
void add(int x,int y)
{
t[++l]=y;
next[l]=last[x];
last[x]=l;
}
int get(int x) {return x==Dad[x]?x:Dad[x]=get(Dad[x]);}
void dfs(int x)
{
if (mark[x]) Dad[x]=x;
else Dad[x]=f[x];
for (int k=last[x];k;k=next[k])
if (t[k]!=f[x]) f[t[k]]=x,dfs(t[k]);
}
int main()
{
read(n);read(Q);
fo(i,1,n-1) read(x),read(y),add(x,y),add(y,x);
fo(i,1,Q)
{
scanf("%s%d",&ch,&a[i]);
if (ch[0]=='C') mark[a[i]]++,bz[i]=true;
}
mark[1]++;
dfs(1);
fd(i,Q,1)
if (bz[i])
{
mark[a[i]]--;
if (mark[a[i]]==0) Dad[a[i]]=f[a[i]];
}
else ans[i]=get(a[i]);
fo(i,1,Q) if (!bz[i]) printf("%d\n",ans[i]);
return 0;
}