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]树】

在标记时可以记录每一个标记操作的时间戳。对于每一个查询操作,我们按这个次序完成。

  1. 如果本节点已被标记,那么直接返回。
  2. 如果已经访问过,返回记忆的值。
  3. 记忆已被访问
  4. 查找下一个,并且记忆答案。

理论时间复杂度时 \(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;
}
posted @ 2022-03-05 15:31  蒟蒻xiezheyuan  阅读(26)  评论(0编辑  收藏  举报