22/6/2

 无向图的双连通分量:eg1:acwing 395.冗余路径(e-dcc);eg2:acwing 1183.电力(v-dcc);eg3:acwing 396.矿场搭建;


无向图的双连通分量:

一些概念:

桥(也叫割边):

割点: 

①:边连通分量(e-dcc):

极大的不含有桥的连通块被称为边双连通分量。
图中的两个边双连通分量之间一定有桥
每个桥至少属于两个边双连通分量

②:点连通分量(v-dcc):

极大的不包含割点的连通块被称为点双连通分量。
每个割点至少属于两个点双连通分量。
图中的两个点双连通分量之间一定有割点

e-dcc算法实现:

和求有向图的强连通分量类似,引入时间戳的概念;

如何找到桥:

设桥的两个端点为x,y则满足dfn[x]<low[y];

如何找到e-dcc:

①:将所有的桥边删去,剩下的连通区域都是e-dcc;

②:和有向图gcc类似,用栈来做;

eg1:冗余路径;

题意其实就是:给一个无向连通图,问最少加几条边会变成边双连通分量; 

先求e-dcc,然后缩点,因为在连通分量内部加边是无意义的,所有缩点,缩完点后,很明显原来的图就变成了一棵树,要使该树变成边双连通分量,所以度数为1的点(叶子节点)都要加边,最少是要加叶子节点个数/2(上取整)个;

 code:

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=a;i<n;++i) 
#define rep2(i,a,n) for( int i=a;i<=n;++i) 
#define per1(i,n,a) for( int i=n;i>a;i--) 
#define per2(i,n,a) for( int i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define pb push_back
#define pf push_front
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define YES cout<<"YES\n"
#define NO cout<<"NO\n"
#define Yes cout<<"Yes\n"
#define No cout<<"No\n"
#define yes cout<<"yes\n"
#define no cout<<"no\n"
#define yi first
#define er second
using namespace std;
typedef pair<int,int> PII;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long ll;
typedef double dob;
const int N=5e3+10,M=2e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],dcc_cnt,top;
int is_birdge[M],id[N],d[N];
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;
    stk[++top]=u;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j,i);
            low[u]=min(low[u],low[j]);
            if(dfn[u]<low[j])
            is_birdge[i]=is_birdge[i^1]=1;
        }
        else if(i!=(from^1))low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        ++dcc_cnt;
        int y;
        do
        {
            y=stk[top--];
            id[y]=dcc_cnt;
        }while(y!=u);
    }

}
signed main()
{
    quick_cin();
    cin>>n>>m;
    memset(h,-1,h);
    while(m--)
    {
        int a,b;cin>>a>>b;
        add(a,b),add(b,a);
    }
    tarjan(1,-1);
    rep1(i,0,idx)
    if(is_birdge[i])d[id[e[i]]]++;
    int cnt=0;
    rep2(i,1,dcc_cnt)
    if(d[i]==1)cnt++;
    cout<<(cnt+1)/2;
    return 0;
}

eg2:电力;

v-dcc算法实现;

如何判断割点:

要求low[y] > dfn[x] y能到达的最早的节点>x的时间戳
否则low[y] ≤ dfn[x]时,即y能到比xdfs序小的点,则存在环

low[y]≤dfn[x]的情况下
1 如果x不是根节点 那么x是割点
2 x是根节点 至少有两个子节点yi 
            且有low[yi]≥dfn[x]
            如果只有一个子结点 去完根节点x
            x   
            |   →   y1  子节点部分还是连通的
            y1
            如果有两个子结点 去完根节点x
            x  
           / \  →   y1  y2 之间不连通
          y1 y2 

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=a;i<n;++i) 
#define rep2(i,a,n) for( int i=a;i<=n;++i) 
#define per1(i,n,a) for( int i=n;i>a;i--) 
#define per2(i,n,a) for( int i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define pb push_back
#define pf push_front
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define YES cout<<"YES\n"
#define NO cout<<"NO\n"
#define Yes cout<<"Yes\n"
#define No cout<<"No\n"
#define yes cout<<"yes\n"
#define no cout<<"no\n"
#define yi first
#define er second
using namespace std;
typedef pair<int,int> PII;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long ll;
typedef double dob;
const int N=1e4+10,M=3e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int root,ans;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++timestamp;
    int cnt=0;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
            if(low[j]>=dfn[u])cnt++;
        }
        else low[u]=min(low[u],dfn[j]);
    }
    if(u!=root)cnt++;
    ans=max(ans,cnt);
}
signed main()
{
    quick_cin();
    while(cin>>n>>m,n||m)
    {
        memset(dfn,0,dfn);
        memset(h,-1,h);
        idx=timestamp=0;
        while(m--)
        {
            int a,b; 
            cin>>a>>b; 
            add(a,b),add(b,a);
        }
        ans=0;
        int cnt=0;
        for(root=0;root<n;++root)
        {
            if(!dfn[root])
            {
                cnt++;
                tarjan(root);
            }
        }
        cout<<ans+cnt-1<<endl;
    }

    return 0;
}

eg3:矿场搭建;

题意:给一个不一定连通的无向图,问最少在几个点设置出口,使得任意一个出口坏掉后,其他所有点都可以和某个出口连通 ;

结论:
1 无割点          放2个出口 方案数 *= C[cnt][2] = cnt*(cnt-1)/2
2 有割点 V-DCC==1 放1个出口 方案数 *= C[cnt-1][1] = cnt-1 (不包含割点)
3 有割点 V-DCC>=2 放0个出口 方案数 *= 1

 

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define rep1(i,a,n) for( int i=a;i<n;++i) 
#define rep2(i,a,n) for( int i=a;i<=n;++i) 
#define per1(i,n,a) for( int i=n;i>a;i--) 
#define per2(i,n,a) for( int i=n;i>=a;i--)
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define memset(a,i,b) memset((a),(i),sizeof (b))
#define memcpy(a,i,b) memcpy((a),(i),sizeof (b))
#define pro_q priority_queue
#define pb push_back
#define pf push_front
#define endl "\n"
#define lowbit(m) ((-m)&(m))
#define YES cout<<"YES\n"
#define NO cout<<"NO\n"
#define Yes cout<<"Yes\n"
#define No cout<<"No\n"
#define yes cout<<"yes\n"
#define no cout<<"no\n"
#define yi first
#define er second
using namespace std;
typedef pair<int,int> PII;
typedef unsigned long long ULL;
typedef pair<long long,long long>PLL;
typedef pair<int,PII> PIII;
typedef long long LL;
typedef double dob;
const int N=1e3+10,M=1e3+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int dcc_cnt;
vector<int>dcc[N];
int cut[N],root;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u;
    if(u==root&&h[u]==-1)
    {
        dcc_cnt++;
        dcc[dcc_cnt].pb(u);
        return;
    }
    int cnt=0;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
            if(dfn[u]<=low[j])
            {
                cnt++;
                if(u!=root||cnt>1)cut[u]=1;
                ++dcc_cnt;
                int y;
                do
                {
                    y=stk[top--];
                    dcc[dcc_cnt].pb(y);
                } while (y!=j);
                dcc[dcc_cnt].pb(u);
            }   
        }
        else low[u]=min(low[u],dfn[j]);
    }
}
signed main()
{
    quick_cin();
    int T=1;
    while(cin>>m,m)
    {
        rep2(i,1,dcc_cnt)dcc[i].clear();
        idx=n=timestamp=top=dcc_cnt=0;
        memset(h,-1,h);
        memset(dfn,0,dfn);
        memset(cut,0,cut);
        while(m--)
        {
            int a,b;
            cin>>a>>b; 
            n=max(n,max(a,b));
            add(a,b),add(b,a);
        }
        for(root=1;root<=n;++root)//这里不能写rep2(root,1,n),会变成局部变量导致错误
        {
            if(!dfn[root])tarjan(root);
        }
        int res=0;
        ULL num=1;
        rep2(i,1,dcc_cnt)
        {
            int cnt=0;
            rep1(j,0,dcc[i].size())
            {
                if(cut[dcc[i][j]])cnt++;
            }
            if(cnt==0)
            {
                if(dcc[i].size()>1)res+=2,num*=dcc[i].size()*(dcc[i].size()-1)/2;
                else res++;
            }
            else if(cnt==1)
            {
                res++;
                num*=dcc[i].size()-1;
            }
        }
        cout<<"Case "<<T++<<": "<<res<<" "<<num<<endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dull丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值