[线段树 + 图论]CF343D Water Tree 题解

其实就是把一些简单操作换到了 dfn 上,然后需要动脑子想一下操作如何转化。

操作一是比较显然的。由于 dfn 是在 dfs 的基础上构成的,你记录一下每个点 \(i\) 的子树大小 \(siz_i\) 然后修改 \(dfn_i\)\(dfn_i + siz_i - 1\) 范围内元素的值就可以了。

操作二不是很好想,需要一点技巧。标记路径用 dfn 不好做,我们可以只标记每次操作二的节点。然后每当你要对一个节点进行操作三的查询时,就看一下它的子树里有没有操作二标记的节点,这样就将路径问题转化成了子树问题,也可以用 dfn 解决了。

我们分别用两颗线段树来记录操作一和操作二。

操作三,对于查询的节点分别查找在操作一和操作二的线段树里的值就行。然而如果一个节点被操作一和操作二分别修改过,如何辨别这两者的先后呢?对于操作一,是权值的操作,我们只能再开一颗线段树记录每个节点最后被修改的时间;对于操作二,由于是标记节点,我们就可以在标记的时候直接将要标记的点改成当前的时间,然后在子树查询时直接查区间最大值。

因此,我们分别需要实现区间修改,单点查询的线段树和单点修改,区间查询最大值的线段树。

#include <bits/stdc++.h>
constexpr int N = 5e5 + 7;
int p , opt , tot , cnt , u , v , n , head[N] , dfn[N] , siz[N] , m;
using namespace std;
struct edge {
	int t , n;
}e[N << 1];
inline void add(int f , int t) {
	e[++cnt].t = t , e[cnt].n = head[f] ,  head[f] = cnt;
}
void dfs(int s , int fa) {
	siz[s] = 1;
	if(!dfn[s]) {
		dfn[s] = ++ tot; //处理一下子树大小和dfn
	}
	for(int i = head[s]; i ; i = e[i].n) {
		int to = e[i].t;
		if(to != fa) {
			dfs(to , s);
			siz[s] += siz[to];
		}
	}
}
struct Segment_Tree1 { //区间修改单点查询的线段树
	int change[N << 2];
	void pushdown(int i) {
		if(change[i]) {
			change[i << 1] = change[i];
			change[i << 1 | 1] = change[i];
			change[i] = 0;
		}
	}
	void modify(int i , int l , int r , int L , int R , int k) {
		if(L <= l && r <= R) {
			change[i] = k;
			return;
		}
		pushdown(i);
		int mid = l + r >> 1;
		if(mid >= L) {
			modify(i << 1 , l , mid , L , R , k);
		}
		if(mid < R) {
			modify(i << 1 | 1 , mid + 1 , r , L , R , k);
		}
	}
	int query(int i , int l , int r , int pos) {
		if(l == r && l == pos) {
			return change[i];
		}		
		pushdown(i);
		int mid = l + r >> 1;
		if(pos <= mid) {
			return query(i << 1 , l , mid , pos);
		}
		else {
			return query(i << 1 | 1 , mid + 1 , r , pos);
		}
	}
}T1 , Time1;
struct Segment_Tree2 { //单点修改,区间查询最大值的线段树
	int Max[N << 2];
	void modify(int i , int l , int r , int pos , int k) {
		if(l == r && l == pos) {
			Max[i] = k; return;
		}
		int mid = l + r >> 1;
		if(pos <= mid) {
			modify(i << 1 , l , mid , pos , k);
		}
		else {
			modify(i << 1 | 1 , mid + 1 , r , pos , k);
		}
		Max[i] = max(Max[i << 1] , Max[i << 1 | 1]);
	}
	int query(int i , int l , int r , int L , int R) {
		if(L <= l && r <= R) {
			return Max[i];
		}
		int res = 0 , mid = l + r >> 1;
		if(mid >= L) {
			res = max(res , query(i << 1 , l , mid , L , R));
		}
		if(mid < R) {
			res = max(res ,  query(i << 1 | 1 , mid + 1 , r , L , R));
		}
		return res;
	}
}T2;
int main() {
	ios :: sync_with_stdio(0) , cin.tie(0) , cout.tie(0);
	cin >> n;
	for(int i = 1; i < n; ++i) {
		cin >> u >> v;
		add(u , v) , add(v , u);
	}
	dfs(1 , 0);
	cin >> m;
	for(register int i = 1; i <= m; ++i) {
		cin >> opt >> p;
		switch(opt) {
			case 1 : {T1.modify(1 , 1 , n , dfn[p] , dfn[p] + siz[p] - 1 , 1); Time1.modify(1 , 1 , n , dfn[p] , dfn[p] + siz[p] - 1 , i); break;}
			case 2 : {T2.modify(1 , 1 , n , dfn[p] , i); break;}
			case 3 : {
				int t1 = Time1.query(1 , 1 , n , dfn[p]);
				int t2 = T2.query(1 , 1 , n , dfn[p] , dfn[p] + siz[p] - 1);
				if(t1 <= t2) {
					cout << 0 << '\n';
				}
				else if(t1 > t2){
					cout << T1.query(1 , 1 , n , dfn[p]) << '\n';
				}
				break;
			}
		}
	}
	return 0;
}
posted @ 2025-05-01 14:41  「癔症」  阅读(11)  评论(0)    收藏  举报