「Tarjan」无向图强连通分量

学习资源

结论(法则)

1.割点判定法则
判断点 x x x 是否为割点,对应边为 x → y x \rightarrow y xy

  1. x x x不为根节点, l o w [ y ] > = d f n [ x ] low[y] >= dfn[x] low[y]>=dfn[x],则 x x x 为割点
  2. x x x 为根节点,存在至少两个点 y y y 使得 l o w [ y ] > = d f n [ x ] low[y] >= dfn[x] low[y]>=dfn[x],则 x x x 为割点

2.桥判定法则
对于边 x → y x \rightarrow y xy,若 l o w [ y ] > d f n [ x ] low[y] > dfn[x] low[y]>dfn[x] 则边 x → y x \rightarrow y xy 为桥
3.无向图添加 ( c n t + 1 ) / 2 (cnt+1)/2 (cnt+1)/2条边能变成边双连通图 (缩点后度为 1 1 1的点的数量 c n t cnt cnt)

Template
// 求桥
int n,m;
struct node{
    int to,ne;
}e[10005 << 1];
int idx = 1;
int h[10005];
int dfn[10005],low[10005],ecc;
int id[10005],tim;
int stk[10005], ins[10005],top;
int sz[10005];
bool isb[10005];


void add(int u,int v){
    e[++idx] = {v,h[u]};
    h[u] = idx;
}

// 记录当前点所在的边的编号
void tarjan(int u,int fa){
    dfn[u] = low[u] = ++tim;
    stk[++top] = u;
    for(int i = h[u];i;i=e[i].ne){
        int v = e[i].to;
        if(!dfn[v]){
            tarjan(v,i);
            low[u] = min(low[u],low[v]);
            if(low[v] > dfn[u]){
                isb[i] = isb[i^1] = 1;
            }
        }
        else if(i != (fa^1)) low[u] = min(low[u],low[v]);
    }
    if(low[u] == dfn[u]){
        ecc++;int y;
        do{
            y = stk[top--];
            id[y] = ecc;
        }while(y!=u);
    } 
}
signed main()
{
    cin>>n>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        if(u == v) continue; // 除去自环
        add(u,v);add(v,u);
    }
    tarjan(1,-1);
    return 0;
}
// 求割点
struct node{
    int to,ne;
}e[1005<<1];
int h[1005];
int idx = 1;
void add(int u,int v){
    e[++idx] = {v,h[u]};
    h[u] = idx;
}
int n;
// 求点连通分量
int dfn[1005],low[1005],dcc,tim;
int stk[1005],top;
bool cut[1005];
vector<int> v[1005];
int root;

// 求vdcc模板
void tarjan(int x){
    dfn[x] = low[x] = ++tim;
    stk[++top] = x;
    if(x == root && !h[x]){
        dcc++;
        v[dcc].push_back(x);
        return;
    }
    int f = 0;
    for(int i = h[x];i;i=e[i].ne){
        int j = e[i].to;
        if(!dfn[j]){
            tarjan(j);
            low[x] = min(low[x],low[j]);
            if(low[j] >= dfn[x]){
                f++;
                if(x != root || f>1 ) cut[x] = 1;
                dcc++;
                int y;
                do{
                    y = stk[top--];
                    v[dcc].push_back(y);
                }while(y!=j);
                v[dcc].push_back(x);
            }
        }
        else low[x] = min(low[x],dfn[j]);
    }
}

signed main()
{
		while(m--){
	        int u,v;
	        cin>>u>>v;
	        if(u == v) continue; // 除去自环
	        add(u,v);add(v,u);
	    }
        // 求 vdcc
        //求割点时root结点非常重要,用来判断割点的条件
        for(root = 1;root <= k;root++){
            if(!dfn[root]) tarjan(root);
        }
    return 0;
}

练习

10098. 「一本通 3.6 例 1」分离的路径

.无向图添加 ( c n t + 1 ) / 2 (cnt+1)/2 (cnt+1)/2条边能变成边双连通图

int n,m;
struct node{
    int to,ne;
}e[10005 << 1];
int idx = 1;
int h[10005];
int dfn[10005],low[10005],ecc;
int id[10005],tim;
int stk[10005], ins[10005],top;
int sz[10005];
bool isb[10005];
void add(int u,int v){
    e[++idx] = {v,h[u]};
    h[u] = idx;
}
void tarjan(int u,int fa){
    dfn[u] = low[u] = ++tim;
    stk[++top] = u;
    for(int i = h[u];i;i=e[i].ne){
        int v = e[i].to;
        if(!dfn[v]){
            tarjan(v,i);
            low[u] = min(low[u],low[v]);
            if(low[v] > dfn[u]){
                isb[i] = isb[i^1] = 1;
            }
        }
        else if(i != (fa^1)) low[u] = min(low[u],low[v]);
    }
    if(low[u] == dfn[u]){
        ecc++;int y;
        do{
            y = stk[top--];
            id[y] = ecc;
        }while(y!=u);
    } 
}
int d[10005];
signed main()
{
    cin>>n>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    tarjan(1,-1);
    forr(i,2,idx){
        if(isb[i]) d[id[e[i].to]]++;
    }
    int cnt = 0;
    forr(i,1,ecc) if(d[i] == 1) cnt++;
    cout << (cnt+1)/2 << endl;
    return 0;
}
10099. 「一本通 3.6 例 2」矿场搭建

无向图,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
至少需要设置几个救援出口,以及不同最少救援出口的设置方案。

统计方案方法代码详见

int t;
struct node{
    int to,ne;
}e[1005<<1];
int h[1005];
int idx = 1;
void add(int u,int v){
    e[++idx] = {v,h[u]};
    h[u] = idx;
}
int n;
// 求点连通分量

int dfn[1005],low[1005],dcc,tim;
int stk[1005],top;
bool cut[1005];
vector<int> v[1005];
int root;



// 求vdcc模板
void tarjan(int x){
    dfn[x] = low[x] = ++tim;
    stk[++top] = x;

    // 求孤立点
    // 根据下面代码可知,求cc需要有儿子,这里需要特判;
    if(x == root && !h[x]){
        dcc++;
        v[dcc].push_back(x);
        return;
    }
    int f = 0;
    for(int i = h[x];i;i=e[i].ne){
        int j = e[i].to;
        if(!dfn[j]){
            tarjan(j);
            low[x] = min(low[x],low[j]);
            if(low[j] >= dfn[x]){
                f++;
                if(x != root || f>1 ) cut[x] = 1;
                dcc++;
                int y;
                do{
                    y = stk[top--];
                    v[dcc].push_back(y);
                }while(y!=j);
                v[dcc].push_back(x);
            }
        }
        else low[x] = min(low[x],dfn[j]);
    }
}



signed main()
{
    while(cin>>n,n){
        forr(i,1,dcc) v[i].clear();
        top = 0;idx = 1,tim=0;
        dcc = 0;
        root = 0;
        mem(dfn,0);mem(low,0);
        mem(h,0);mem(cut,0);
        int k;
        while(n--){
            int u,v;cin>>u>>v;
            root = max({root,u,v});
            add(u,v);add(v,u);
        }

        k = root;
        // 求 vdcc
        
        for(root = 1;root <= k;root++){
            if(!dfn[root]) tarjan(root);
        }

        int res = 0;
        int ans = 1;
        forr(i,1,dcc){
            
            int cnt = 0;
            for(auto j:v[i]){
                if(cut[j]) cnt++;
            }

            if(!cnt) 
                if(v[i].size()) res+=2,ans *= (v[i].size()*(v[i].size()-1))/2;
                else res++;
            else if(cnt == 1) res++, ans *= (v[i].size()-1);

        }
        printf("Case %llu: %llu %llu\n",++t,res,ans);
        
    }
    return 0;
}
10101. 「一本通 3.6 练习 2」嗅探器

给两个点 a , b a,b a,b 问是否存在一个割点使得 a , b a,b a,b 不在一个连通分量里
割点板子题 在判割点的时候多加入一个条件 对于边 x → y x \rightarrow y xy 使得 l o w [ y ] > = l o w [ x ] & & d f n [ y ] < = d f n [ b ] low[y] > =low[x] \&\& dfn[y] <= dfn[b] low[y]>=low[x]&&dfn[y]<=dfn[b]
取自洛谷题解

struct node {
    int to, ne;
} e[200005 << 1];
int h[200005];
int idx = 1;
void add(int u, int v) {
    e[++idx] = {v, h[u]};
    h[u] = idx;
}
int n;
// 求点连通分量

int dfn[200005], low[200005], dcc, tim;
int stk[200005], top;
bool cut[200005];
vector<int> v[200005];
vector<int> id[200005];
int d1, d2;

// 求vdcc模板
void tarjan(int x) {
    dfn[x] = low[x] = ++tim;
    stk[++top] = x;
    int f = 0;

    for (int i = h[x]; i; i = e[i].ne) {
        int j = e[i].to;

        if (!dfn[j]) {
            tarjan(j);
            low[x] = min(low[x], low[j]);

            if (low[j] >= dfn[x]) {
                f++;

                if (x != d1 || f > 1)
                    if (dfn[d2] >= dfn[j])
                        cut[x] = 1;
            }
        } else
            low[x] = min(low[x], dfn[j]);
    }
}


int main() {
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);

    cin >> n;
    int x, y;

    while (cin >> x >> y, x || y) {
        add(x, y);
        add(y, x);
    }

    cin >> d1 >> d2;
    tarjan(d1);

    forr(i, 1, n) {
        if (cut[i]) {
            cout << i << endl;
            return 0;
        }
    }
    puts("No solution");
    return 0;
}
10103. 「一本通 3.6 练习 4」电力

对于一个点 x x x
若存在 c n t cnt cnt 个子结点(搜索树中),若x为根结点贡献为 c n t cnt cnt ,否则 c n t + 1 cnt+1 cnt+1 枚举点求最大贡献

struct node {
    int to, ne;
} e[200005 << 1];
int h[200005];
int idx = 1;
void add(int u, int v) {
    e[++idx] = {v, h[u]};
    h[u] = idx;
}
int n;
// 求点连通分量
int dfn[200005], low[200005], dcc, tim;
int stk[200005], top;
bool cut[200005];
vector<int> v[200005];
vector<int> id[200005];
int d1, d2;
// 求vdcc模板
void tarjan(int x) {
    dfn[x] = low[x] = ++tim;
    stk[++top] = x;
    int f = 0;

    for (int i = h[x]; i; i = e[i].ne) {
        int j = e[i].to;

        if (!dfn[j]) {
            tarjan(j);
            low[x] = min(low[x], low[j]);

            if (low[j] >= dfn[x]) {
                f++;

                if (x != d1 || f > 1)
                    if (dfn[d2] >= dfn[j])
                        cut[x] = 1;
            }
        } else
            low[x] = min(low[x], dfn[j]);
    }
}
int main() {
    cin >> n;
    int x, y;
    while (cin >> x >> y, x || y) {
        add(x, y);
        add(y, x);
    }
    cin >> d1 >> d2;
    tarjan(d1);
    forr(i, 1, n) {
        if (cut[i]) {
            cout << i << endl;
            return 0;
        }
    }
    puts("No solution");
    return 0;
}
10104. 「一本通 3.6 练习 5」Blockade

类似上题

typedef long long ll;
int pri[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
const int inf = 0x3f3f3f3f;
const int INF = ~0ULL;
const int N = 1e6+10;
int n,m;
vector<int> g[100005];
int dfn[100005],low[100005];
int tim;
int ans[100005];
int sz[100005];
bool cut[100005];

void tarjan(int x){
    dfn[x] = low[x] = ++tim,sz[x] = 1;
    int f = 0,sum = 0;
    for(auto v:g[x]){
        if(!dfn[v]){
            tarjan(v);
            sz[x] += sz[v];
            low[x] = min(low[x],low[v]);
            if(low[v] >= dfn[x]){
                f++;
                ans[x] += sz[v]*(n-sz[v]);
                sum += sz[v];
                if(x!=1 || f > 1) cut[x] = 1;
            }
        }
        else low[x] = min(low[x],dfn[v]);
    }
    if(cut[x]) ans[x] += (n-1-sum)*(sum+1)+n-1;
    else ans[x] = 2*(n-1);
}


signed main()
{
    cin>>n>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        if(u==v) continue;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    tarjan(1);

    forr(i,1,n) cout << ans[i] << endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值