【ACWing】1183. 电力

题目地址:

https://www.acwing.com/problem/content/1185/

给定一个由 n n n个点 m m m条边构成的无向图,请你求出该图删除一个点之后,连通块最多有多少。

输入格式:
输入包含多组数据。每组数据第一行包含两个整数 n , m n,m n,m。接下来 m m m行,每行包含两个整数 a , b a,b a,b,表示 a , b a,b a,b两点之间有边连接。数据保证无重边。点的编号从 0 0 0 n − 1 n−1 n1。读入以一行 0   0 0\ 0 0 0结束。

输出格式:
每组数据输出一个结果,占一行,表示连通块的最大数量。

数据范围:
1 ≤ n ≤ 10000 1≤n≤10000 1n10000
0 ≤ m ≤ 15000 0≤m≤15000 0m15000
0 ≤ a , b < n 0≤a,b<n 0a,b<n

可以用类似求割点的Tarjan算法来做,但是本题里只需要直接求最多能产生多少个连通块就行了,不需要真的把割点都求出来。假设总共有 k k k个连通块,并且对于某个连通块里的某个点 u u u,去掉它能将其所在连通块变成 l l l个,那么总共个数就是 l + k − 1 l+k-1 l+k1。所以关键问题就是求 l l l。同求桥一样,从某个点 u u u开始DFS,也开两个数组dfnlowdfn[v]表示第一次走到 v v v的时候的时间戳,low[v]表示从 v v v不经过DFS树上的点能走到的时间戳最小的点的时间戳(最后一步走到了DFS树上的点是可以的)。那么如果存在 u ↔ v u\leftrightarrow v uv这条边,并且DFS是从 u u u走到 v v v的,并且low[v] >= dfn[u]的话,则说明 v v v无法绕到 u u u上方,那么删去 u u u之后, v v v所在点全在 u u u下方的连通块就会独立出来,那么分出的连通块的个数的计数应该加 1 1 1。并且,对于 u u u是否是树根的情况要特判,如果 u u u非树根,那么 u u u的上方也会多出一个连通块,此时总计数要把 u u u上面产生的那个连通块给补上;如果 u u u恰好是树根的话则无此问题(这里其实考虑了 u u u不是割点的情况,如果 u u u不是割点也不是树根,那么它没有孩子能分出点全在 u u u下面的连通块,但是删掉 u u u也能产生 1 1 1个连通块)。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 10010, M = 30010;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int res;

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void tarjan(int u, int from) {
    dfn[u] = low[u] = ++timestamp;

	// cnt记录的是删掉u之后,u所在连通块能分出多少个连通块
    int cnt = 0;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            // v绕不到u上方,所以v能分出一个全在u下面的连通块
            if (low[v] >= dfn[u]) cnt++;
        } else low[u] = min(low[u], dfn[v]);
    }
	
	// u不是DFS树根,那么上面也能分出一块出来,所以cnt要加1
    if (u != from) cnt++; 
	
    res = max(res, cnt);
}

int main() {
    while (scanf("%d%d", &n, &m), n || m) {
        memset(dfn, 0, sizeof dfn);
        memset(h, -1, sizeof h);
        idx = timestamp = 0;

        while (m--) {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }
        
        // res存每个连通块在删掉枚举的点的情况下,最多能分裂成多少个小连通块
        res = 0;
        // 这里的cnt计算的是连通块的个数
        int cnt = 0;
        for (int i = 0; i < n; i++)
            if (!dfn[i]) {
                cnt++;
                tarjan(i, i);
            }
        
        printf("%d\n", res + cnt - 1);
    }

    return 0;
}

时间复杂度 O ( n + m ) O(n+m) O(n+m),空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值