Graph Destruction---反向建图

题目

Problem Statement

Given is an undirected graph with NN vertices and MM edges.
Edge ii connects Vertices A_iAi​ and B_iBi​.

We will erase Vertices 1, 2, \ldots, N1,2,…,N one by one.
Here, erasing Vertex ii means deleting Vertex ii and all edges incident to Vertex ii from the graph.

For each i=1, 2, \ldots, Ni=1,2,…,N, how many connected components does the graph have when vertices up to Vertex ii are deleted?

Constraints

  • 1 \leq N \leq 2 \times 10^51≤N≤2×105
  • 0 \leq M \leq \min(\frac{N(N-1)}{2} , 2 \times 10^5 )0≤M≤min(2N(N−1)​,2×105)
  • 1 \leq A_i \lt B_i \leq N1≤Ai​<Bi​≤N
  • (A_i,B_i) \neq (A_j,B_j)(Ai​,Bi​)=(Aj​,Bj​) if i \neq ji=j.
  • All values in input are integers.

Input

Input is given from Standard Input in the following format:

NN MM
A_1A1​ B_1B1​
A_2A2​ B_2B2​
\vdots⋮
A_MAM​ B_MBM​

Output

Print NN lines.
The ii-th line should contain the number of connected components in the graph when vertices up to Vertex ii are deleted.


Sample Input 1 Copy

Copy

6 7
1 2
1 4
1 5
2 4
2 3
3 5
3 6

Sample Output 1 Copy

Copy

1
2
3
2
1
0

这道题比较有意思,是考的一个统计连通块的问题,但是题目给出的条件需要绕一个弯。

题目给出一个建好的图,然后每次删掉节点,问你这时图中有几个连通块,连通块不难想到要用并查集求解,但是每次删掉一个是什么鬼,并查集可没说谁跟谁连在一起啊喂,所以你没法根据并查集然后根据删掉节点的去维护连通块个数。

再想一想其实并查集就是不断做加法的过程,而题目给出的是减法,那减法反过来不就是加法了,所以考虑反向建图,动态维护

考虑当前正在考虑删掉 i 节点,那么也就是反向建图从 n 建到 i+1 节点时的连通块数量,所以每次的,所以其实就是用 i 时的连通块数量来更新 ans[ i-1 ] 的值,ans[]也就是答案数组,显然ans[n]=0,因为删掉最后一个一定是1,同时读入数据存储的方式也要改一下,虽然当前是无向图,但是其实存边只需要存小的指向大的那一边就可以了,因为在建大顶点时小的还没有加入。

来考虑中间过程,设num为连通块数量,初值0,从第n-1个顶点开始计算(循环的是加入的顶点所以从n开始)。假设当前删到第i-1个,那么就是计算反向建图加入 i 的时候的连通块数量。可以先假设 i 顶点是孤立的,这时候连通块数量+1,每次遇到一个跟新加顶点不一样的就让连通块-1,因为两个合为了一个,把结束时的num放进对应的ans [i-1] 重复这样的过程就可以得到答案。

下面是中间的更新连通块的过程。

要注意的点就是合并连通块要把代表块找出来合并(跌倒无数次........)

for (int i = n; i >= 2; i--) {
	num++;//假设先是孤立的
	//printf("%d\n", v[i].size());
	if (v[i].size()) {//有边
		for (int j = 0; j < v[i].size(); j++) {
			int index = v[i][j];//取出坐标
			if (get_jz(index) != get_jz(i)) {
				num--;//合二为一,连通块减少
				jz[get_jz(index)] = jz[i];
			}
		}
	}
	ans[i-1] = num;
}

完整代码

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>v[200100];
#define ll long long
int jz[200010];//每个块先指向自己
int ans[200010];

int get_jz(int t) {
	if (jz[t] == t)return t;
	return jz[t] = get_jz(jz[t]);
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)jz[i] = i;
	int x, y;
	while (m--) {
		scanf("%d%d", &x, &y);
		v[x].push_back(y);
	}
	ans[n] = 0;
	ans[n - 1] = 1;
	int num=0;//只有一个
	for (int i = n; i >= 2; i--) {
		num++;//假设先是孤立的
		//printf("%d\n", v[i].size());
		if (v[i].size()) {//有边
			for (int j = 0; j < v[i].size(); j++) {
				int index = v[i][j];//取出坐标
				if (get_jz(index) != get_jz(i)) {
					num--;
					jz[get_jz(index)] = jz[i];
				}
			}
		}
		ans[i-1] = num;
	}
	for (int i = 1; i <= n; i++)printf("%d\n", ans[i]);
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值