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]);
}

浙公网安备 33010602011771号