题目描述
给出一个 $n$ 个孤立点的图,每个点上的权值都是 $0$,进行 $m$ 次操作
操作 $1$ :把两个点所在的连通块合并起来
操作 $2$ :向某个点所在的连通块的所有点累加一个值
$n \leq 10^4, m \leq 10^5$
最后输出每个点上的权值
并查集 树上差分
我们通过并查集合并连通块,保证同一个连通块内的点同属一个集合
对于每一个合并操作,找到两个点所属的集合
如果这两个点不在同一连通块,那么我们构造一个新点,使这个新点成为集合合并后的根节点
这样进行 $k$ 次有效合并操作后,就会产生 $k$ 个新点
我们所构造的图是若干棵树,编号为 $1-n$ 的节点都是树的叶子节点
对于每次连通块累加操作,我们只需要向集合的根节点累加一个值即可
最后对我们所构造出来的一堆树DP(只是遍历一下),把每个点的权值下放到子树中的所有节点中
然后依次输出编号为 $1-n$ 的节点的权值即可
时间复杂度 $O(n + m)$
C++ 代码
#include<cstdio>
#include<cstring>
const int N = 200010, M = N << 1;
int h[N], e[M], ne[M], idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int n, m;
int p[N];
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int f[N];
void dfs(int u, int fa)
{
f[u] += f[fa];
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j, u);
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n * 2; i ++) p[i] = i;
int root = n + 1;
while(m --)
{
int op, a, b;
scanf("%d%d%d", &op, &a, &b);
if(op == 1)
{
a = find(a), b = find(b);
if(a != b)
{
p[a] = p[b] = root;
add(root, a);
add(root, b);
root ++;
}
}
else
{
a = find(a);
f[a] += b;
}
}
for(int i = n + 1; i < root; i ++)
if(p[i] == i) dfs(i, 0);
for(int i = 1; i <= n; i ++)
printf("%d ", f[i]);
return 0;
}
优雅不过如此!
java版本如下:
佬 咱数组定义能写下注释不 代码写得很漂亮 有注释就更好了wuwuwu
真的是太贴心了,先不管看不看的懂,我直接点赞
给大佬跪了orz
优雅
不理解的话可以看我下面的补充,这样就可以很好的理解了,非高手可以看看。
厉害
建立新节点这个很好,我之前也想到建树,但没有想到它们指向新的节点
tql!清晰明了!
官方困难,滑稽大佬的简单%%%
太牛逼了
这个思路太牛了,怎么想到的
orz
实在是miao
orz
牛波一
orz
太优雅了
orz
Orz
太 牛了 我的又臭又长
for(int i = n + 1; i < root; i ++)
if(p[i] == i) dfs(i, 0);
这一步没懂 难道说 每一条树建立完 数就不变了吗??
从根节点开始
妙啊~ 构造一个新点当合并后的根节点