题目
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;
}