P4092 [HEOI2016/TJOI2016] 树 解题报告

题目链接

题意

给定一颗有根树,根为 \(1\),有以下两种操作:

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

  • 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖先)。

\(1 \leqslant N, Q \leqslant 100000\)

思路

考虑并查集。

发现正着做不好做,加标记很麻烦,于是可以反着来。

读入时可以记录每一个点的标记次数,然后从 \(1\) 号节点 dfs 一遍。

如果这个点被标记了,那么他的并查集祖先就为他自己。

如果这个点没有被标记,那么他的并查集祖先就为他的父亲。

然后倒序处理询问,如果是标记操作,那么这个点的标记次数减少。

如果减后的标记次数为 \(0\),那么这个点的并查集祖先就重新设为他的父亲。

如果是查询操作,那么直接查即可,记录答案(因为我们是离线做法)。

注意事项

  • 需要路径优化,不然会 TLE(原本我以为路径优化后会破坏树的结构从而影响答案的)

代码

/*
	在读入时记录每个点被染色的次数,之后从根节点dfs。
	如果这个点被染色了,就让并查集数组的值为自己,否则为他的父亲。
	然后倒序枚举操作。
	如果是查询操作,直接find()这个点,得到的值就是最近的被染色的祖先。
	如果是标记操作,则删除这个标记,即让这个点被染色的次数减1。
	如果染色次数变成了0,就意味着这个点没有染色了,将并查集数组的值改为它的父亲。
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5; 
int n, q, f[N], col[N], ans[N], fa[N], num;
vector<int> ve[N];
struct query
{
	int type, x;
}a[N];
void dfs(int x, int lst)
{
	fa[x] = lst;
	if(col[x] == 0)
	{
		f[x] = lst;
	}
	else f[x] = x;
	for(int i = 0; i < ve[x].size();i ++)
	{
		int v = ve[x][i];
		if(v == lst) continue;
		dfs(v, x);
	}
}
int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}
signed main()
{
	cin >> n >> q;
	col[1] ++;
	f[1] = fa[1] = 1;
	for(int i = 1; i < n; i ++)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	for(int i = 1; i <= q; i ++)
	{
		char c; int x;
		cin >> c >> x;
		if(c == 'Q')
		{
			a[i].type = 1;
			a[i].x = x;
		}
		else
		{
			a[i].type = 2;
			a[i].x = x;
			col[x] ++;
		}
	}
	dfs(1, 1);
	for(int i = q; i >= 1; i --)
	{
		if(a[i].type == 1)
		{
			ans[++ num] = find(a[i].x);
		}
		else
		{
			col[a[i].x] --;
			if(col[a[i].x] == 0) f[a[i].x] = fa[a[i].x];
		}
	}
	for(int i = num; i >= 1; i --) printf("%d\n", ans[i]);
}
posted @ 2026-06-24 16:00  Luowj  阅读(5)  评论(0)    收藏  举报