AcWing 2069. 网络分析 (并查集,树上差分)

小明正在做一个网络实验。

他设置了 n n n 台电脑,称为节点,用于收发和存储数据。

初始时,所有节点都是独立的,不存在任何连接。

小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。

两个节点如果存在网线连接,称为相邻。

小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。

所有发送和接收的节点都会将信息存储下来。

一条信息只存储一次。

给出小明连接和测试的过程,请计算出每个节点存储信息的大小。

输入格式
输入的第一行包含两个整数 n,m,分别表示节点数量和操作数量。

节点从 1 1 1 n n n 编号。

接下来 m m m 行,每行三个整数,表示一个操作。

如果操作为 1 a b,表示将节点 a a a 和节点 b b b 通过网线连接起来。当 a = b a = b a=b 时,表示连接了一个自环,对网络没有实质影响。
如果操作为 2 p t,表示在节点 p p p 上发送一条大小为 t t t 的信息。

输出格式
输出一行,包含 n n n 个整数,相邻整数之间用一个空格分割,依次表示进行完上述操作后节点 1 1 1 至节点 n n n 上存储信息的大小。

数据范围
1 ≤ n ≤ 10000 , 1≤n≤10000, 1n10000,
1 ≤ m ≤ 1 0 5 , 1≤m≤10^5, 1m105,
1 ≤ t ≤ 100 1≤t≤100 1t100

输入样例1:

4 8
1 1 2
2 1 10
2 3 5
1 4 1
2 2 2
1 1 2
1 2 4
2 2 1

输出样例1:

13 13 5 3

本题做法参考至题解

单纯用并查集这道题是没有办法解决的,因为不光需要合并集合,同时也要求了查询某一点。
对于此可以使用树上差分。

可以在合并集合的过程中同时建立起多颗树,这时候就可以保证信息量的加法仅加在集合中,并且能过查询单点。

在增加信息量的操作时,我们先给那些建立的根节点加上,然后再最后遍历所有根节点,让每棵树的值通过根节点一步步传递到叶节点,最终打成连通增加信息量的效果。

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 10;
const int 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 cnt[N];

int find(int x) {
	if (p[x] != x)p[x] = find(p[x]);
	return p[x];
}

void dfs(int u, int f) {
	cnt[u] += cnt[f];//cnt[u]:传入的节点的值
	//cnt[f]:传入的父节点的值,可以观察到后面每一层传入的都是u,都是根节点
	// 
	//遍历树,并且让每一颗树的子树节点都加上父节点存储的值
	for (int i = h[u]; i != -1; i = ne[i]) {
		dfs(e[i], u);//搜下一层
	}
}

int main() {
	memset(h, -1, sizeof h);
	cin >> n >> m;
	for (int i = 1; i <= n * 2; i++)p[i] = i;
	int root = n + 1;

	while (m--) {
		int op, a, b; cin >> op >> a >> b;
		if (op == 1) {
			a = find(a), b = find(b);
			//这里要提前把a和b的祖宗提取出来,不然在下边建树的过程中会导致爆栈(不知道为啥)
			if (a != b) {
				p[a] = p[b] = root;
				add(root, a);
				add(root, b);
				root++;//之后来的点建树就要另换一颗树
			}
		}
		else {
			cnt[find(a)] += b;//这里先只让祖宗节点加
		}
	}

	//扫所有的根节点
	for (int i = n + 1; i < root; i++)
		if (p[i] == i)	//如果这个根节点的祖先是自己,也就是说他是最根部的根节点
			dfs(i, 0);	//那就扫遍整个树,并且使这棵树上所有的点加上自己的信息量,然后再一层层传递到叶节点
						//这里需要加一层判断是因为上述过程中n+1之后的节点也有可能被合并到其他的根节点上
						//传入i,0,因为根节点自己不需要加,i作为根节点传入

	for (int i = 1; i <= n; i++)cout << cnt[i] << " ";
	return 0;
}
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值