CodeForces 620E New Year Tree 线段树

本文介绍了一种利用线段树优化树形DP的方法,解决了改变子树颜色及查询子树中不同颜色数量的问题。通过DFS序记录子树范围,并采用二进制表示颜色存在状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

E. New Year Tree
time limit per test
3 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

The New Year holidays are over, but Resha doesn't want to throw away the New Year tree. He invited his best friends Kerim and Gural to help him to redecorate the New Year tree.

The New Year tree is an undirected tree with n vertices and root in the vertex 1.

You should process the queries of the two types:

  1. Change the colours of all vertices in the subtree of the vertex v to the colour c.
  2. Find the number of different colours in the subtree of the vertex v.
Input

The first line contains two integers n, m (1 ≤ n, m ≤ 4·105) — the number of vertices in the tree and the number of the queries.

The second line contains n integers ci (1 ≤ ci ≤ 60) — the colour of the i-th vertex.

Each of the next n - 1 lines contains two integers xj, yj (1 ≤ xj, yj ≤ n) — the vertices of the j-th edge. It is guaranteed that you are given correct undirected tree.

The last m lines contains the description of the queries. Each description starts with the integer tk (1 ≤ tk ≤ 2) — the type of the k-th query. For the queries of the first type then follows two integers vk, ck (1 ≤ vk ≤ n, 1 ≤ ck ≤ 60) — the number of the vertex whose subtree will be recoloured with the colour ck. For the queries of the second type then follows integer vk (1 ≤ vk ≤ n) — the number of the vertex for which subtree you should find the number of different colours.

Output

For each query of the second type print the integer a — the number of different colours in the subtree of the vertex given in the query.

Each of the numbers should be printed on a separate line in order of query appearing in the input.

Examples
Input
7 10
1 1 1 1 1 1 1
1 2
1 3
1 4
3 5
3 6
3 7
1 3 2
2 1
1 4 3
2 1
1 2 5
2 1
1 6 4
2 1
2 2
2 3
Output
2
3
4
5
1
2
题意:一棵树上面的点都有颜色,最多61种,有两种操作,一种是把这个点以及它子树的所有节点都涂成那个颜色,另一个操作是问这个节点和它的子树里颜色数

思路:建立一棵线段上,用dfs序记录下子树范围,然后维护线段树的时候,用二进制来表示某个颜色是否存在,有61种颜色,也就是2^61次方就可以表示完所有情况,对于一个点维护出来的数,每一位都去对比,然后记录颜色数就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define mem(a,x) memset(a,x,sizeof(a))
#define lowbit(x) (x & (-x))
#define maxn 500005
vector<int>vec[maxn];
int vis[maxn],le[maxn],ri[maxn],a[maxn],cnt;
ll ans;

struct node{
	int le,ri;
	ll num,lazy;
}tree[maxn * 4];

void dfs(int x){  // dfs序,建立各个点到其所有子树 的编号 
	vis[x] = 1;
	le[x] = ++cnt;
	for(int i = 0;i < vec[x].size();i++){
		int v = vec[x][i];
		if(vis[v])
			continue;
		dfs(v);
	}
	ri[x] = cnt;
}
void push_down(int i){
	if(!tree[i].lazy)
		return ;
	tree[i * 2].num = tree[i * 2 + 1].num = (1ll << tree[i].lazy);
	tree[i * 2].lazy = tree[i * 2 + 1].lazy = tree[i].lazy;
	tree[i].lazy = 0;
}
void push_up(int i){
	tree[i].num = tree[i * 2].num | tree[i * 2 + 1].num;
}
void build(int i,int l,int r){
	tree[i].lazy = 0;
	tree[i].le = l;
	tree[i].ri = r;
	if(l == r)
		return ;
	int mid = (l + r) >> 1;
	build(i * 2,l,mid);
	build(i * 2 + 1,mid + 1,r);
}
void get_pos(int i,int aim,int w){
	if(tree[i].le == aim && tree[i].ri == aim){
		tree[i].num = (1ll << w);
		return ;
	}
	push_down(i);
	int mid = (tree[i].le + tree[i].ri) >> 1;
	if(aim <= mid)
		get_pos(i * 2,aim,w);
	if(aim > mid)
		get_pos(i * 2 + 1,aim,w);
	push_up(i); 
}
void update(int i,int l,int r,ll w){
	if(tree[i].le == l && tree[i].ri == r){
		tree[i].lazy = w;
		tree[i].num = (1ll << w); //更新颜色 
		return ;
	}
	push_down(i);
	int mid = (tree[i].le + tree[i].ri) >> 1;
	if(r <= mid)
		update(i * 2,l,r,w);
	else if(l > mid)
		update(i * 2 + 1,l,r,w);
	else{
		update(i * 2,l,mid,w);
		update(i * 2 + 1,mid + 1,r,w);
	}
	push_up(i);
	return ;
}
void query(int i,int l,int r){
	if(tree[i].le == l && tree[i].ri == r){
		ans |= tree[i].num;
		return ;
	}
	push_down(i); //懒惰数组,查询到了再更新 
	int mid = (tree[i].le + tree[i].ri) >> 1;
	if(r <= mid){
		query(i * 2,l,r);
	}else if(l > mid){
		query(i * 2 + 1,l,r);
	}else{
		query(i * 2,l,mid);
		query(i * 2 + 1,mid + 1,r);
	}
	push_up(i);
}
int main(){
	int n,m,u,v;
	scanf("%d %d",&n,&m);
	for(int i = 1;i <= n;i++)
		scanf("%d",&a[i]);
	for(int i = 1;i < n;i++){
		scanf("%d %d",&u,&v);
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	cnt = 0;
	dfs(1);
	build(1,1,cnt);
	for(int i = 1;i <= n;i++){
		get_pos(1,le[i],a[i]); //把每个点的颜色上传,且转化为二进制保存在线段树里 
	}
	while(m--){
		int opt,x,y;
		scanf("%d",&opt);
		if(opt == 1){
			scanf("%d %d",&x,&y);
			update(1,le[x],ri[x],y);
		}else{
			ans = 0;
			scanf("%d",&x);
			query(1,le[x],ri[x]); // 查询得到一个ans 
			int tmp = 0;
			for(int i = 1;i <= 61;i++){	//去与61个位一一比较,统计颜色数 
				if(ans & (1ll << i))
					tmp++;
			}
			printf("%d\n",tmp);
		}
	}
	return 0;
} 
### 线段树优化建图的实现方法与应用 线段树优化建图是一种在图论中用于处理大规模区间连边问题的技术,尤其适用于最短路、网络流等场景。其核心思想是利用线段树的结构来减少节点和边的数量,从而降低时间和空间复杂度。 #### 实现方法 在线段树优化建图中,每个点通常被分为入点(in)和出点(out)。例如,对于一个点 $ u $,将其拆分为 $ u_{\text{in}} $ 和 $ u_{\text{out}} $。接下来,构建两棵线段树:**入树**(维护入点)和**出树**(维护出点)[^2]。 - **出树**中的非根节点向其父节点连一条权值为0的有向边。 - **入树**中的非叶子节点向其左右儿子连一条权值为0的有向边。 - 对于原图中的每个点,连接一条从出点到入点的无向边,以防止一些异常情况的发生。 当需要对某个区间进行连边时,可以通过线段树的结构快速定位相关节点并建立连接。例如: - 如果是从一个点向另一个点连边,则直接连接对应的两个叶子节点。 - 如果是从一个点向一个区间连边,则将该点的出点连接到入树中对应区间的节点。 - 如果是从一个区间向一个点连边,则将出树中对应区间的节点连接到该点的入点。 - 如果是从一个区间向另一个区间连边,则引入一个虚拟节点,分别从出树中的节点连接到虚拟节点,并从虚拟节点连接到入树中的节点[^4]。 这种方法避免了传统暴力建图中 $ O(MN^2) $ 的时间复杂度,大大提升了效率。 #### 应用场景 线段树优化建图广泛应用于以下场景: 1. **最短路径问题**:如 Codeforces Round #406 (Div. 1) B. Legacy 题目中,使用线段树优化建图可以高效地处理区间连边问题,从而求解最短路径[^4]。 2. **网络流问题**:在某些网络流模型中,尤其是在涉及大量区间操作的情况下,线段树优化建图能够显著减少图的规模,提高算法效率[^2]。 3. **2-SAT问题**:在某些复杂的2-SAT问题中,线段树优化建图可以帮助更高效地处理变量之间的约束关系,例如 ARC069F Flags 问题中就使用了线段树优化建图结合二分法求解[^5]。 #### 示例代码 以下是一个简单的线段树优化建图的伪代码示例,展示如何构建出树并连接边: ```python class SegmentTreeNode: def __init__(self, left, right): self.left = left self.right = right self.left_child = None self.right_child = None self.parent = None def build_segment_tree(l, r): node = SegmentTreeNode(l, r) if l == r: return node mid = (l + r) // 2 node.left_child = build_segment_tree(l, mid) node.right_child = build_segment_tree(mid + 1, r) node.left_child.parent = node node.right_child.parent = node # 出树中非根节点向父节点连边(权值为0) add_edge(node.left_child, node, 0) add_edge(node.right_child, node, 0) return node def add_edge(u, v, weight): # 添加从u到v的有向边,权值为weight pass ``` 上述代码仅展示了出树的构建过程,实际应用中还需要构建入树,并根据具体问题添加相应的边。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值