[洛谷2664] 树上游戏

本文介绍了一种在树形结构数据上的高效算法——树上差分,通过将特定颜色的点从树中删除形成小树,利用深度最小的节点进行树上差分,实现了O(n)的时间复杂度。文章详细阐述了算法的实现过程,包括如何求解小树的大小,以及如何使用虚树优化算法。

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

传送门

看起来像是点分,其实有 O ( n ) O(n) O(n) 做法?
其实我就是不想写点分才这么写结果感觉脑子烧掉了

考虑一种颜色对答案的贡献。

考虑把树中这种颜色的点都删掉,那么就会有很多的小树,这些小树中的点互相之间不会产生贡献,而不同树的两个点之间会产生贡献。

由此,我们可以得到每一种颜色,点的sum值就是 n - 所在小树的size。

由此,一个点总的sum就是 n * 颜色数 - 每种颜色时所在小树的size。

我们考虑对于一棵小树的size,存在深度最小的节点上,那么后面就可以用树上差分实现覆盖。

嗯,大概用虚树是可以的,可是我不会
所以size怎么求?我也不会讲,乱搞就好了

#include<bits/stdc++.h>
#define LL long long
#define re register
#define fr(i,x,y) for(int i=(x);i<=(y);i++)
#define rf(i,x,y) for(int i=(x);i>=(y);i--)
#define frl(i,x,y) for(int i=(x);i<(y);i++)
using namespace std;
const int N=100002;
const int M=N<<1;
int n,a[N];
int cnt,head[N],Next[M],v[M];

inline void read(int &x){
	char ch=getchar();x=0;
	for(;ch<'0'||ch>'9';ch=getchar());
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
}

void add(int x,int y){
	Next[++cnt]=head[x];
	head[x]=cnt;
	v[cnt]=y;
}

int sz[N],f[N],pre[N];
LL tag[N],c[N];
void dfs(int x,int fa){
	sz[x]=1;f[x]=pre[a[x]];
	for(re int i=head[x];i;i=Next[i])
	 if (v[i]!=fa){
	 	pre[a[x]]=v[i];
	 	dfs(v[i],x);
	 	tag[v[i]]+=sz[v[i]];
	 	sz[x]+=sz[v[i]];
	 }
	tag[f[x]]-=sz[x];
	if (f[x]==1) c[a[x]]-=sz[x];
	pre[a[x]]=f[x];
}

LL sum[N];
void dfs(int x,int fa,LL s){
	int cc=c[a[x]];
	s+=tag[x]-cc;
	sum[x]=s;
	for(re int i=head[x];i;i=Next[i])
	 if (v[i]!=fa){
	 	c[a[x]]=tag[v[i]];
	 	dfs(v[i],x,s);
	 }
	c[a[x]]=cc;
}

int b[N];
int main(){
	read(n);
	fr(i,1,n) read(a[i]),b[a[i]]=1;
	int x,y;
	fr(i,2,n){
		read(x);read(y);
		add(x,y);add(y,x);
	}
	//fr(i,1,100000) ver[i].push_back(0);
	int tot=0;
	fr(i,1,100000) if (b[i]) tot++,pre[i]=1,c[i]=n;
	tag[1]=1LL*tot*n;
	dfs(1,0);
	dfs(1,0,0);
	fr(i,1,n) printf("%lld\n",1LL*n*tot-sum[i]);
	return 0;
}
<think>首先,用户的问题是关于洛谷 P3367 的解题代码和实现思路。用户提到了“洛谷 P3367”,这是一个编程题目编号,我需要提供相关的程序实现或解题思路。 从系统指令中,我有一些引用:[^1], [^2], [^3]。这些引用提供了关于洛谷 P3367 的信息。引用[1] 提供了C++代码,引用[2] 提供了Java题解,包括时间复杂度和优化说明,引用[3] 讨论了合并操作。 用户的具体问题是:“请问洛谷 P3367 解题代码 实现思路”。所以,我需要提供解题代码和实现思路。 洛谷 P3367 的题目是“【模板】并查集”,这是一个关于并查集(Union-Find)数据结构的模板题。并查集用于处理集合的合并与查询问题。 我的回答结构需要清晰,帮助用户逐步解决问题。既然用户要求“解题代码”和“实现思路”,我应该先解释思路,再提供代码。 从引用中: - 引用[1] 提供了C++代码。 - 引用[2] 提供了Java题解和优化说明。 - 引用[3] 讨论了合并操作。 我需要确保回答真实可靠,所以基于这些引用提供信息。 实现思路: - 并查集的核心操作包括:查找(find)、合并(union)、检查连接(check)。 - 优化:使用路径压缩(path compression)来优化查找操作。 - 时间复杂度:查找和合并操作近似O(α(n)),其中α是反阿克曼函数,几乎是常数时间。 - 空间复杂度:O(n),用于存储父节点数组。 代码:我可以提供C++代码,因为引用[1] 有完整的代码。引用[1] 的代码是: ```cpp #include<iostream> #include<algorithm> #include<vector> using namespace std; const int N = 2e5+10; int fa[N]; int find(int i) { if(fa[i] != i) { fa[i] = find(fa[i]); } return fa[i]; } void set(int u,int v) { int u1 = find(u); int v1 = find(v); if(u1 != v1) { fa[v1] = u1; } } bool check(int u,int v) { return find(u) == find(v); } int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int n,m; cin >> n >> m; for(int i = 1 ; i <= N ; i++) fa[i] = i; for(int i = 1 ; i <= m ; i++) { int z,x,y; cin >> z >> x >> y; if(z == 1) { set(x,y); } else if(z == 2) { if(check(x,y)) { cout << &#39;Y&#39; << &#39;\n&#39;; } else { cout <<&#39;N&#39; << &#39;\n&#39;; } } } return 0; } ``` 在回答中,我需要: - 使用中文回答。 - 对于数学表达式,使用$...$或$$...$$格式。但在并查集上下文中,可能没有太多数学,但如果有,比如时间复杂度,我可以使用行内数学。 - 生成相关问题:在最后,添加相关问题部分。 - 引用的段落末尾添加引用标识,如[^1]。 结构: 1. **实现思路**:解释并查集的基本概念和操作。 2. **解题代码**:提供C++代码(或可选其他语言),并解释关键部分。 3. **相关问题**:生成一些相关问题。 相关问题应该基于主题,比如: 1. 并查集的其他优化(如按秩合并)。 2. 并查集的应用场景。 3. 如何测试并查集的性能。 最后,确保引用正确:在解释思路时,如果基于引用,添加标识。 现在,构建回答: - 开头:介绍题目。 - 思路:描述并查集的find, union, check操作,优化(路径压缩),时间复杂度。 - 代码:提供C++代码,并简要说明。 - 相关问题:列出几个问题。 引用添加:例如,当提到路径压缩时,引用[2]提到优化说明。</think>### 洛谷 P3367 【模板】并查集解题思路与代码 洛谷 P3367 是一个经典的并查集(Union-Find)模板题,用于处理集合的合并与查询操作。并查集是一种高效的数据结构,常用于解决动态连通性问题,如网络连接检测或图论中的组件合并。下面我将逐步解释实现思路,并提供完整的解题代码。 #### 实现思路 1. **核心操作**: - **查找(Find)**:确定元素所属的集合代表(根节点)。使用路径压缩优化(Path Compression),在查找过程中将节点直接连接到根节点,减少后续查找深度。时间复杂度近似 $O(\alpha(n))$,其中 $\alpha$ 是反阿克曼函数,可视为常数时间[^2]。 - **合并(Union)**:将两个元素所在的集合合并为一个集合。先查找各自的根节点,然后连接它们(例如,将其中一个根节点指向另一个)。时间复杂度同样为 $O(\alpha(n))$[^2]。 - **检查(Check)**:判断两个元素是否属于同一集合(即是否连通),通过比较它们的根节点实现。时间复杂度 $O(\alpha(n))$[^2]。 2. **优化方法**: - **路径压缩**:在查找操作中扁平化树结构,确保后续操作更快。引用[1] 的代码中,`find` 函数递归更新父节点来实现这一点。 - **高效 I/O**:处理大规模输入时,使用缓冲读写(如 C++ 中的 `ios::sync_with_stdio(0)`)避免超时[^1]。 - **初始化**:每个元素初始化为自己的父节点(即独立集合),空间复杂度 $O(n)$[^2]。 3. **整体流程**: - 读取输入:元素数量 $n$ 和操作数 $m$。 - 处理操作:根据指令类型($z=1$ 为合并,$z=2$ 为查询)调用相应函数。 - 输出结果:对于查询操作,输出 "Y"(连通)或 "N"(不连通)。 #### 解题代码(C++实现) 以下代码基于引用[1] 的实现,并添加了详细注释。代码使用路径压缩优化,确保高效性。 ```cpp #include <iostream> #include <algorithm> using namespace std; const int N = 2e5 + 10; // 最大元素数量,根据题目要求设置 int fa[N]; // 存储每个元素的父节点 // 查找操作:带路径压缩 int find(int i) { if (fa[i] != i) { fa[i] = find(fa[i]); // 递归压缩路径 } return fa[i]; } // 合并操作:将元素u和v所在的集合合并 void setUnion(int u, int v) { int uRoot = find(u); // 找到u的根节点 int vRoot = find(v); // 找到v的根节点 if (uRoot != vRoot) { fa[vRoot] = uRoot; // 将v的根节点指向u的根节点(简单合并) } } // 检查操作:判断u和v是否连通 bool check(int u, int v) { return find(u) == find(v); // 比较根节点是否相同 } int main() { ios::sync_with_stdio(0); // 加速输入输出 cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; // 读取元素数n和操作数m // 初始化:每个元素为独立集合 for (int i = 1; i <= n; i++) { fa[i] = i; } // 处理m个操作 for (int i = 0; i < m; i++) { int z, x, y; cin >> z >> x >> y; // 读取操作类型和参数 if (z == 1) { setUnion(x, y); // 合并操作 } else if (z == 2) { // 查询操作,输出结果 if (check(x, y)) { cout << "Y\n"; } else { cout << "N\n"; } } } return 0; } ``` **代码说明**: - **时间复杂度**:所有操作(查找、合并、检查)平均为 $O(\alpha(n))$,近似常数时间,适合处理大规模数据(如 $n \leq 2 \times 10^5$)[^2]。 - **空间复杂度**:$O(n)$,仅需存储父节点数组。 - **关键优化**:路径压缩在 `find` 函数中实现,显著提升性能。引用[3] 强调合并操作通过根节点连接来避免树退化[^3]。 - **输入输出**:使用缓冲读写加速,避免超时(常见于洛谷在线评测)。 #### 性能与应用 - **性能分析**:在随机数据下,并查集效率极高,但最坏情况(如链状结构)可能退化。路径压缩确保平均高效。 - **应用场景**:图论连通性问题(如 Kruskal 算法中的最小生成树)、动态网络连接检测或游戏开发中的对象分组[^2]。 如果您有其他语言需求(如 Java),我可以基于引用[2] 提供实现。或者,您需要优化(如按秩合并)的代码,请告知!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值