AOJ 2170 Marked Ancestor 并查集 好题! 有助于理解路径压缩的意义

You are given a tree T that consists of N nodes. Each node is numbered from 1 to N, and node 1 is always the root node of T. Consider the following two operations on T:
M v: (Mark) Mark node v.
Q v: (Query) Print the index of the nearest marked ancestor of node v which is nearest to it. Initially, only the root node is marked. Note that a node is an ancestor of itself.
Your job is to write a program that performs a sequence of these operations on a given tree and calculates the value that each Q operation will print. To avoid too large output file, your program is requested to print the sum of the outputs of all query operations. Note that the judges confirmed that it is possible to calculate every output of query operations in a given sequence.
Input
The input consists of multiple datasets. Each dataset has the following format:
The first line of the input contains two integers N and Q, which denotes the number of nodes in the tree T and the number of operations, respectively. These numbers meet the following conditions: 1 ≤ N ≤ 100000 and 1 ≤ Q ≤ 100000.
The following N - 1 lines describe the configuration of the tree T. Each line contains a single integer pi (i = 2, … , N), which represents the index of the parent of i-th node.
The next Q lines contain operations in order. Each operation is formatted as “M v” or “Q v”, where v is the index of a node.
The last dataset is followed by a line containing two zeros. This line is not a part of any dataset and should not be processed.
Output
For each dataset, print the sum of the outputs of all query operations in one line.
Sample Input
6 3
1
1
2
3
3
Q 5
M 3
Q 5
0 0
Output for the Sample Input
4

题目大意:
题意:给出一颗树,有两种操作:
1、mark u 标记结点u
2、query u 询问离u最近的且被标记的祖先结点是哪个
输出所有询问的和

解题思路:
本来看到这题时,也没想太多,就认为这是并查集与树相结合的问题,根据输入的父结点关系建立并查集,标记节点v即将该节点的子树分离。然后就写了一个并查集的代码,然后就AC了。。。。后来看了讨论区以及网上大神们的分析,感觉这样确实不太合理,最坏情况下为整棵树成为一条链,单纯使用并查集查询复杂度最坏是1e10 估计是数据比较水的缘故。
下面是正解:
记录下所有被标记结点的最早被标记时间,然后将所有询问以及询问的时间点记录下来并倒序处理。
如果我们倒着处理询问,那么我们处理完一个询问以后,时间上在这个询问之后的标记操作便都可以忽略掉了,因为这些标记操作将不会影响剩余的查询。并查集的路径压缩刚好可以做到上述的‘忽略’操作。在处理一个询问的过程中,并查集寻根操作时会经过很多未标记点和很多‘应忽略标记’的点,这些点我们都可以直接忽略掉而让被询问点直接指向离它最近的、并且标记尚未过期的点,同时我们也可以让本次寻根路径上的其余点也直接指向那个离询问点最近的、并且标记尚未过期的点。这样就进行了树的重构,使得树高不断减小,从而降低了询问花费的时间。

这个树不断重构的过程确实能够很好地反映出并查集路径压缩的重要意义。
AC代码:

#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
#define INF 0x3fffffff
int pre[100005];//用于保存树形结构
int mark[100005];//用于存放各结点最早的标记时间
typedef pair<int, int>P;
P Q[100005];//second 表示查询的时间  first表示查询的对象
void init(int n) {
	for (int i = 1; i <= n; i++) {
		pre[i] = -1;
		mark[i] = INF;
	}
	//pre[1] = 1;
	mark[1] = 0;//事先标记根结点
}
int findroot(int x, int y) {
	if (mark[x] < y) {//该点在查询之前已经标记
		return x;
	}
	else {
		int tmp = findroot(pre[x], y);
		/*路径压缩 按时间顺序倒序处理查询
		那么该时间之后的mark操作就没有意义了,可直接进行路径压缩
		将路径上的点进行路径压缩,直到到达在该时间之前的一次mark操作那里
		*/
		pre[x] = tmp;
		return tmp;
	}
}

int main() {
	int n, m;
	while (scanf("%d%d", &n, &m) != EOF) {
		if (n == 0 && m == 0) break;
		init(n);
		for (int i = 2; i <= n; i++) {
			scanf("%d", &pre[i]);
		}
		long long ans = 0;
		int cnt = 0;//查询的个数
		for (int i = 1; i <= m; i++) {
			char op; int tmp;
			scanf("\n%c%d", &op, &tmp);
			if (op == 'M') {
				mark[tmp] = min(i, mark[tmp]);//存放最早的标记时间
			}
			else if (op == 'Q') {
				Q[++cnt] = P(tmp, i);
			}
		}
		for (; cnt > 0; cnt--) {
			ans += findroot(Q[cnt].first, Q[cnt].second);
		}
		cout << ans << endl;
	}
}

最开始的简单并查集AC代码。。。

#include<iostream>
#include<stdio.h>
using namespace std;
int pre[100005];
void init(int n) {
	for (int i = 1; i <= n; i++) {
		pre[i] = -1;
	}
	pre[1] = 1;
}
int findroot(int x) {
	if (pre[x] == x) { return x; }
	else {
		/*int tmp = findroot(pre[x]);
		pre[x] = tmp;//路径压缩
		return tmp;*/
		return findroot(pre[x]);
	}
}
int main() {
	int n, m;
	while (scanf("%d%d", &n, &m)!=EOF) {
		if (n == 0 && m == 0) break;
		init(n);
		for (int i = 2; i <= n; i++) {
			scanf("%d", &pre[i]);
		}
		long long ans = 0;
		for (int i = 1; i <= m; i++) {
			char op; int tmp;
			scanf("\n%c%d", &op, &tmp);
			if (op == 'M') {
				pre[tmp] = tmp;
			}
			else if (op == 'Q') {
				ans += findroot(tmp);
			}
		}
		cout << ans << endl;
	}
}

解题过程中参考的大神博客:
Aizu - 2170 Marked Ancestor 并查集
大神太强了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值