P4092 [HEOI2016/TJOI2016]树
题面
在 2016 年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树,根为 \(1\) ,有以下两种操作:
-
标记操作:对某个结点打上标记。(在最开始,只有结点 \(1\) 有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)
-
询问操作:询问某个结点最近的一个打了标记的祖先。(这个结点本身也算自己的祖先)
你能帮帮她吗?
思路
1.暴力DFS图遍历
可以考虑建反图,然后标记用一个数组维护,询问则以该节点为源点,像下(原图为上)跑DFS遍历,直到 遍历到被标记的节点即可。
由于是树,所以不会出现死循环的情况。
时间复杂度为 \(O(nq)\)。
#include <bits/stdc++.h>
#define SIZE ((int(1e5))+5)
using namespace std;
int n, q, ec, juli = INT_MAX, ans;
int head[SIZE];
int tag[SIZE];
struct edge {
int next, to;
} edges[SIZE];
void add(int from, int to) {
edges[++ec].next = head[from];
edges[ec].to = to;
head[from] = ec;
}
void dfs(int u, int ndeep) {
if (tag[u] > 0 && juli > ndeep) {
ans = u;
juli = ndeep;
}
for (int j = head[u]; j; j = edges[j].next) {
int v = edges[j].to;
dfs(v, ndeep + 1);
}
}
int main() {
cin >> n >> q;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(v, u);
}
tag[1] = 1;
while (q--) {
char op;
int u;
cin >> op >> u;
if (op == 'C') {
tag[u] = 1;
} else {
dfs(u, 0);
cout << ans << endl;
juli = INT_MAX;
}
}
}
能水 \(68\) 分。
2.并查集+DFS初始化
建双向边。初始化时图遍历建并查集,将每次查询的图遍历操作换成并查集的检索即可。
时间复杂度是 \(O(q\log n+n)\)?(不会分析)
#include <bits/stdc++.h>
#define SIZE ((int(1e5))+5)
using namespace std;
int n, q, ec, juli = INT_MAX, ans;
int head[SIZE];
int tag[SIZE];
struct edge {
int next, to;
} edges[SIZE<<1];
int fa[SIZE];
void add(int from, int to) {
edges[++ec].next = head[from];
edges[ec].to = to;
head[from] = ec;
}
void dfs(int now, int f) {
fa[now] = f;
for (int i = head[now]; i; i = edges[i].next) {
int u = edges[i].to;
if (u == f) continue;
dfs(u, now);
}
}
int main() {
cin >> n >> q;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(v, u);
add(u, v);
}
dfs(1,1);
tag[1] = 1;
while (q--) {
char op;
int x;
cin >> op >> x;
if (op == 'C') {
tag[x] = 1;
} else {
if (tag[x]) {
cout << x << endl;
continue;
}
while (!tag[fa[x]]) {
x = fa[x];
}
cout << fa[x] << endl;
}
}
return 0;
}
能水 \(92\) 分。
3.并查集+时间戳优化(记忆化DFS)
时间戳(\(\text{Time Stamp}\))是指记录每一个操作的次序(时间戳)以便下一步操作。
参考资料:题解 P4092 【[HEOI2016/TJOI2016]树】
在标记时可以记录每一个标记操作的时间戳。对于每一个查询操作,我们按这个次序完成。
- 如果本节点已被标记,那么直接返回。
- 如果已经访问过,返回记忆的值。
- 记忆已被访问
- 查找下一个,并且记忆答案。
理论时间复杂度时 \(O(n^2)\),可是数据使得时间复杂度并没有达到峰值。可以A。
#include <bits/stdc++.h>
#define retunr return
#define SIZE ((int(1e5))+5)
using namespace std;
int tag[SIZE],fa[SIZE];
int stamp[SIZE];
int parent[SIZE];
int nstamp,n,q;
int find(int u){
if(tag[u]){
return u;
}
else if(stamp[u]==nstamp){
return parent[u];
}
parent[u]=nstamp;
retunr parent[u]=find(fa[u]);
}
void biaoji(int u){
if(!tag[u]){
tag[u]=1;
nstamp++;
}
}
int main(){
cin>>n>>q;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
fa[v]=u;
}
fa[1]=1;
biaoji(1);
for(int i=1;i<=n;i++){
stamp[i]=1;
parent[i]=1;
}
while(q--){
char op;
int u;
cin>>op>>u;
if(op=='C'){
biaoji(u);
}
else{
cout<<find(u)<<endl;
}
}
return 0;
}