Marked Ancestor

一道并查集的题目硬是被我当成线段树写了,感觉这样写虽然不是最好的,不过能a就行

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=103906#problem/G

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2170

首先题意:

一个可以被标记的树,起初只有根节点被标记了,然后有两种操作,第一种操作是把某一个点标记,第二个操作是询问他的祖先中离他最近的节点编号,最后出书询问的节点编号和.


这是我在并查集专题上面遇到的,不过第一反应暴力肯定不行(然而暴力的并查集查找祖先也可以过...),然后想半天没思路,因为当前节点的询问过后不能直接路径压缩,因为这样后面如果该条路径有一个点被标记那就gg了

但此时想法是如果这个点被标记了,那么他的后代可能都会受到影响,但是又不能一个一个去更新影响,想到这里再加上题目中的两种操作更新查询和线段树神似,所以就开始搞线段树,但是这里有一个问题:线段树更新的点都在一起...,接着想到以前一道没做出来的题(翻硬币类似的)用了dfsn重建树然后再更新维护,所以就用先序遍历了一下,同时我记录下了每个节点的深度,因为更新的时候,深度更深的点应该把深度浅的点信息更新掉(为啥?举一个抽象例子,如果点A被B和C更新了,那么肯定B和C是A的祖先,如果depB<depC,那么depC离A更近)

其他的都是细节问题了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <queue>
#include <cstdlib>
#include <algorithm>
#include <stack>
#include <map>
#include <queue>
#include <vector>

using namespace std;
const int maxn = 1e5+100;
const int INF = 0x3f3f3f3f;
#define pr(x)      // cout << #x << " = " << x << " ";
#define prln(x)    // cout << #x << " = " << x <<endl;
#define ll long long
int head[maxn], nxt[maxn], to[maxn], dfsn, cnt, id[maxn], r[maxn], _n, sum[maxn<<2], dep[maxn];
//建最初的树
void addedge(int u, int v) {
	nxt[cnt] = head[u];
	head[u] = cnt;
	to[cnt++] = v;
}
//线段树等初始化
void init(int n) {
	cnt = dfsn = 0;
	_n = 1;
	while(_n < n) _n = _n*2;
	int _nn = _n*2;
	for(int i = 0; i <= _nn; ++i) sum[i] = -1;
	for(int i = 0; i <= n;++i) {
		head[i] =-1;
	}
}
//dfs建树
//这里保证了一个点的后代都在一起被访问到,最左边的点是这个点id[u]表示最左边点//的编号,r[u]表示u后代最右边点编号
void dfstree(int fa,int u) {
	id[u] = ++dfsn;
	dep[u] = dep[fa]+1;
	for(int i = head[u]; ~i; i = nxt[i]){
		dfstree(u,to[i]);
	}
	r[u] = dfsn;
}
//这里是线段树的更新操作
//sum初始化为-1,如果当前未被更新或者更新的点深度小于v2这个点深度,,那么更新
inline void getans(int& ans, const int& v2){
	if(ans == -1 || dep[ans] < dep[v2]) ans = v2;
}
//其他和正常线段树都一样了
void pushdown(int rt) {
	if(sum[rt] != -1) {
		getans(sum[rt<<1], sum[rt]);
		getans(sum[rt<<1|1],sum[rt]);
	}
}
void update(int rt, int l, int r, int ql, int qr, int v) {
	if(ql <= l && r <= qr) {
		getans(sum[rt], v);
		return;
	}
	pushdown(rt);
	int m = l + r >> 1;
	if(m >= ql) update(rt<<1, l, m, ql, qr, v);
	if(m < qr) update(rt<<1|1, m+1, r, ql, qr, v);
}
//这里查询稍微改了一下,直接向上找
int query(int rt) {
	rt += _n-1;
	int ans = 0;
	while(rt>=1) {
		getans(ans,sum[rt]);
		rt = rt>>1;
	}
	return ans;
}
int main(){
#ifdef LOCAL
    freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin);
  //freopen("C:\\Users\\Administrator\\Desktop\\out.txt","w",stdout);
 #endif
    int n, m, x;
    char op[10];
    while(cin >> n >> m && (n||m)) {
    	ll ans = 0;
    	init(n);
    	for(int i = 2; i <= n; ++i) {
    		scanf("%d", &x);
    		addedge(x,i);
    	}
    	dep[0] = 0;
    	dfstree(0,1);
    	update(1, 1, _n, id[1], r[1], 1);
    	for(int i = 0; i < m; ++i) {
    		scanf("%s%d", op, &x);
    		if(op[0] == 'M')	update(1, 1, _n, id[x], r[x], x);
    		else	ans += query(id[x]);
    	}
    	printf("%lld\n", ans);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值