#1190 : 连通性·四
题意:求多少个点连通分量。
对于一个无向图的子图,当删除其中任意一个点后,不改变图内点的连通性,这样的子图叫做点的双连通子图。而当子图的边数达到最大时,叫做点的双连通分量。
思路:每存在一个割点,就把一个区域一分为二。点的双连通分量就等于割点数量加1。对于分割到最后的图,最后一组点连通向量就是根节点所在的连通向量。(在这里想了好久,被hiho官方的题解误导,官方的题解是将割点分成两种情况讨论的,一种是根节点的情况,另外是非根结点的情况,实际统计的时候只会统计到非根结点分割图得出来的结果,对于最后一组点连通分量,因为图已经被分割成最后一个连通向量了,所以最后一组连通分量不再存在割点,即根结点所在的连通分量就是最后一组点连通分量)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200000 + 10;
const int INF=int(1e9);
struct Edge {
int u, v;
int id;
int next;
};
struct tarjan { //tarjan模板
int n;
int dfn[maxn]; //dfn数组是个时间戳,就是访问那个节点的时间,也就是第几次调用dfs这个函数
int low[maxn]; //Low是u的子节点能通过反向边到达的节点DFN的最小值, 初始值为dfn[u]
int index; //记录dfs调用的次数
int edge[maxn]; //记录割边
int node[maxn]; //记录割点
int f[maxn]; //父亲节点
int head[maxn]; //前向星的写法
bool vis[maxn]; //标记边是否访问过
int fa[maxn]; //每一条边属于的组的编号
int top; //前向星记录边数
int tol; //模拟存边的栈的下标移动
int cnt; //点连通分量的个数
Edge e[maxn]; //前向星记录边数
int es[maxn]; //模拟存边的堆栈
int temp[maxn]; //模拟队列,记录一个点连通分量的边
void init(int N) { //初始化
n = N;
index = 0;
top = 0;
tol = 0;
cnt = 0;
memset(dfn, -1, sizeof(dfn));
memset(f, 0, sizeof(f));
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(fa, -1, sizeof(fa));
}
void addedge(int u, int v,int i) { //添加边
e[top].u = u;
e[top].v = v;
e[top].id = i;
e[top].next = head[u];
head[u] = top++;
}
void dfs(int u) { //dfs类似环缩点的写法,两个主要数组dfn[]和low[]
dfn[u] = low[u] = ++index;
bool flag = false;
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
int id = e[i].id;
if (vis[id])
continue;
if (dfn[v] == -1) {
f[v] = u;
es[tol++] = id;
dfs(v);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) { //low[v] >= dfn[u],表示u是一个割点
cnt++; //为什么没有特判根节点呢?我个人理解为因为分割的图后,根节点所在连通分量即为一个点连通分量
int edg = -1;
int Min=INF;
int scnt=0;
while (edg != id) {
edg = es[--tol];
Min=min(Min,edg);
vis[edg] = 1;
temp[scnt++]=edg; //将边存在一个队列中再处理
}
for(int i=0; i<scnt; i++)
fa[temp[i]]=Min;
}
} else if (v != f[u]) {
es[tol++] = id;
low[u] = min(low[u], dfn[v]);
}
}
}
void solve() { //遍历所有点
for (int i = 1; i <= n; i++) {
if (dfn[i] == -1)
dfs(i);
}
}
} T;
int main() {
int n, m;
int u, v;
scanf("%d %d", &n, &m);
T.init(n);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &u, &v);
T.addedge(u, v,i);
T.addedge(v, u,i);
}
T.solve();
cout << T.cnt << endl;
for (int i = 1; i<=m; i++) {
printf("%d%c", T.fa[i], i == m ? '\n' : ' ');
}
return 0;
}