[双连通+割点+桥]无向图连通性问题专题

最近一直搞专辑,所以没写啥解题报告,今天终于搞完了一个,小结一下下

题目来源,LightOJ,在Problem Category里的Graph Theory里的Articulation/Bridge/Biconnected Component

1026 - Critical Links

裸题,找桥用tarjan算法即可解决

代码:

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
struct Brige{
    int u,v;
    Brige(){}
    Brige(int uu,int vv):u(uu),v(vv){}
    bool operator < (const Brige &b) const{
        if(u!=b.u)return u<b.u;
        else return v<b.v;
    }
};
vector<int> g[MAXN];
vector<Brige> brige;
int low[MAXN],dfn[MAXN],depth,n,t;
void init(){
    brige.clear();
    for(int i=0;i<MAXN;i++)g[i].clear();
    depth = 1;
    memset(dfn,-1,sizeof(dfn));
}
void tarjan(int fa,int u){
    low[u] = dfn[u] = depth++;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        if(dfn[v]==-1){
            tarjan(u,v);
            low[u] = min(low[u],low[v]);
            if(low[v]>dfn[u]){
                if(u<v){
                    brige.push_back(Brige(u,v));
                }else{
                    brige.push_back(Brige(v,u));
                }
            }
        }else if(v!=fa){
            low[u] = min(low[u],dfn[v]);
        }
    }
}
int main(){
    scanf("%d",&t);
    for(int c=1;c<=t;c++){
        printf("Case %d:\n",c);
        init();
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            int node,m;
            scanf("%d (%d)",&node,&m);
            for(int i=0;i<m;i++){
                int adj;
                scanf("%d",&adj);
                g[node].push_back(adj);
            }
        }
        for(int i=0;i<n;i++){
            if(dfn[i]==-1)tarjan(-1,i);
        }
        printf("%d critical links\n",brige.size());
        sort(brige.begin(),brige.end());
        for(int i=0;i<brige.size();i++){
            printf("%d - %d\n",brige[i].u,brige[i].v);
        }
    }
    return 0;
}


1063 - Ant Hills

求割点数,裸题,代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
vector<int> g[MAXN];
int low[MAXN],dfn[MAXN],cut[MAXN],tot,t,n,m,depth;
void init(){
    memset(dfn,0,sizeof(dfn));
    memset(cut,0,sizeof(cut));
    depth = 1;tot = 0;
    for(int i=0;i<MAXN;i++)g[i].clear();
}
void tarjan(int fa,int u){
    low[u] = dfn[u] = depth++;
    int son = 0;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        if(!dfn[v]){
            tarjan(u,v);
            son++;
            low[u] = min(low[u],low[v]);
            if((u==1&&son>=2)||(u!=1&&dfn[u]<=low[v])){
                if(!cut[u])cut[u]=1,tot++;
            }
        }else if(v!=fa){
            low[u] = min(dfn[v],low[u]);
        }
    }
}
int main(){
    scanf("%d",&t);
    for(int c=1;c<=t;c++){
        init();
        scanf("%d%d",&n,&m);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
            //cout<<a<<" "<<b<<endl;
        }
        tarjan(-1,1);
        printf("Case %d: %d\n",c,tot);
    }
    return 0;
}


1291 - Real Life Traffic

求出所有的边双连通分量,即缩点,然后计算缩点以后图度数为1个结点的个数N,答案就是(N+1)/2,可以证明

不过简单的方法用一次tarjan就可以解决,代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 10010;
int low[MAXN],deg[MAXN],dfn[MAXN],t,n,m,depth,cnt;
vector<int> g[MAXN];
void init(){
    for(int i=0;i<MAXN;i++){
        g[i].clear();
    }
    memset(deg,0,sizeof(deg));
    memset(dfn,0,sizeof(dfn));
    depth = 1;cnt = 0;
}
void tarjan(int fa,int u){
    low[u] = dfn[u] = depth++;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        if(!dfn[v]){
            tarjan(u,v);
            low[u] = min(low[u],low[v]);
            if(low[v]>dfn[u]){//让值从子节点更新上来,根的信息已经无法继续更新,所以可以直接使用
                deg[v]++;
                deg[u]++;
                if(deg[v]==1)cnt++;
            }else{
                deg[u]+=deg[v];
            }
        }else if(v!=fa){
            deg[u]+=deg[v];
            low[u] = min(low[u],dfn[v]);
        }
    }
}
int main(){
    scanf("%d",&t);
    for(int c=1;c<=t;c++){
        init();
        scanf("%d%d",&n,&m);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        tarjan(-1,1);
        if(deg[1]==1)cnt++;
        printf("Case %d: %d\n",c,(cnt+1)/2);
    }
    return 0;
}


1300 - Odd Personality

此题有些蛋疼,题目是让求所有在奇圈上点的个数,其中奇圈中一个点可以出现多次,但是每一条边只能出现一次,咋一看很像POJ的Knight Of the Round Table,但是这里加了一个条件(一个点可使用多次),所以它不是在点双连通分量里面找奇圈,而是在边双连通分量里找奇圈(因为这个WA无数次,蛋碎),如何判断奇圈还是通过交叉染色法。

解题步骤:

1.用tarjan找出所有的边双连通分量

2.用交叉染色法对每一组边双连通分量进行交叉染色(染色失败即存在奇圈,那么该边双连通分量中的每一个点都在一个奇圈上,这条性质可以证明)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 10010
#define MAXM 20010
vector<int> g[MAXN],gt[MAXN];
vector<int>::iterator ite;
int col[MAXN],good[MAXN],low[MAXN],dfn[MAXN],t,n,m,depth,flag;
void init(){
    memset(gt,0,sizeof(gt));
    memset(col,0,sizeof(col));
    memset(good,0,sizeof(good));
    memset(dfn,0,sizeof(dfn));
    for(int i=0;i<MAXN;i++)g[i].clear(),gt[i].clear();
    depth = 1;
}
void tarjan(int fa,int u){
    low[u] = dfn[u] = depth++;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        if(!dfn[v]){
            tarjan(u,v);
            low[u] = min(low[u],low[v]);
            if(low[v]>dfn[u]){
                for(ite=gt[u].begin();ite!=gt[u].end();ite++){
                    if(*ite==v){
                        gt[u].erase(ite);
                        break;
                    }
                }
                for(ite=gt[v].begin();ite!=gt[v].end();ite++){
                    if(*ite==u){
                        gt[v].erase(ite);
                        break;
                    }
                }
            }
        }else if(v!=fa&&dfn[v]<dfn[u]){
            low[u] = min(low[u],dfn[v]);
        }
    }
}
//交叉染色
void dfs_paint(int u,int c){
    col[u] = c;
    for(int i=0;i<(int)gt[u].size();i++){
        int v = gt[u][i];
            if(!col[v]){
                dfs_paint(v,-c);
            }else if(col[v]==col[u]){
                flag = 1;
            }
    }
}
//DFS标记answer
void dfs(int u){
    dfn[u] = good[u] = 1;
    for(int i=0;i<(int)gt[u].size();i++){
        int v = gt[u][i];
        if(!dfn[v])dfs(v);
    }
}
int main(){
    scanf("%d",&t);
    for(int c=1;c<=t;c++){
        init();
        scanf("%d%d",&n,&m);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
            gt[a].push_back(b);
            gt[b].push_back(a);
        }
        for(int i=0;i<n;i++)if(!dfn[i])tarjan(-1,i);
        memset(dfn,0,sizeof(dfn));
        for(int i=0;i<n;i++){
            if(!col[i]){
                flag = 0;
                dfs_paint(i,1);
                if(flag){
                    dfs(i);
                }
            }
        }
        int ans = 0;
        for(int i=0;i<n;i++){
            if(good[i])ans++;
        }
        printf("Case %d: %d\n",c,ans);
    }
    return 0;
}

1308 - Ant Network

此题也比较蛋碎,因为有一个特殊情况要处理,而且貌似需要用大数(个人比较挫,认为mod 2^64次方需要大数)

题目意思是:给定一个连通的无向图,在里面标记一些点,使得这个图在去除任意一个点后,所有的点和被标记的点是连通的,求出最少要标记多少个点,然后求出不同的方案数mod 2^64

其实可以发现,如果原图存在割点,那么只需要在每一个只含有一个割点的点双连通分量中把除割点以外的任意一个点标记即可,然后方案数就是所有这样的点双连通分量中结点个数-1的乘积(不含割点所以-1),然后有一个特殊情况。。。(这里蛋碎了),如果原图不存在割点,那么只需要标记两个点就可以了,方案数就是结点总数*(结点总数-1)/2。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#include<algorithm>
#include<cstring>
using namespace std;
/
//big int
const int base = 10000;
const int width = 4;
const int N = 1000;
struct bint{
    int ln,v[N];
    bint(int r=0){
        for(ln = 0;r>0;r/=base)v[ln++] = r%base;
    }
    bint operator = (const bint &r){
        memcpy(this,&r,(r.ln+1)*sizeof(int));
        return *this;
    }
};
bool operator < (const bint &a,const bint &b){
    int i;
    if(a.ln!=b.ln)return a.ln<b.ln;
    for(i=a.ln-1;i>=0&&a.v[i]==b.v[i];i--);
    return i<0?0:a.v[i]<b.v[i];
}
bool operator <= (const bint& a,const bint& b){
    return !(b<a);
}
bint operator + (const bint &a, const bint &b){
    bint res;int i,cy=0;
    for(i=0;i<a.ln||i<b.ln||cy>0;i++){
        if(i<a.ln)cy+=a.v[i];
        if(i<b.ln)cy+=b.v[i];
        res.v[i] = cy%base; cy/=base;
    }
    res.ln = i;
    return res;
}
bint operator - (const bint &a,const bint &b){
    bint res;int i,cy=0;
    for(res.ln=a.ln,i=0;i<res.ln;i++){
        res.v[i] = a.v[i]-cy;
        if(i<b.ln)res.v[i]-=b.v[i];
        if(res.v[i]<0)cy=1,res.v[i]+=base;
        else cy = 0;
    }
    while(res.ln>0&&res.v[res.ln-1]==0)res.ln--;
    return res;
}
bint operator * (const bint &a,const bint &b){
    bint res;res.ln = 0;
    if(0==b.ln){res.v[0]=0;return res;}
    int i,j,cy;
    for(i=0;i<a.ln;i++){
        for(j=cy=0;j<b.ln||cy>0;j++,cy/=base){
            if(j<b.ln)cy+=a.v[i]*b.v[j];
            if(i+j<res.ln)cy+=res.v[i+j];
            if(i+j>=res.ln)res.v[res.ln++] = cy%base;
            else res.v[i+j] = cy%base;
        }
    }
    return res;
}
bint operator / (const bint &a,const bint &b){
    bint tmp,mod,res;
    int i,lf,rg,mid;
    mod.v[0] = mod.ln = 0;
    for(i=a.ln-1;i>=0;i--){
        mod = mod*base+a.v[i];
        for(lf=0,rg=base-1;lf<rg;){
            mid = (lf+rg+1)/2;
            if(b*mid<=mod)lf=mid;
            else rg = mid-1;
        }
        res.v[i] = lf;
        mod = mod-b*lf;
    }
    res.ln = a.ln;
    while(res.ln>0&&res.v[res.ln-1]==0)res.ln--;
    return res;
}
bint operator % (const bint &a,const bint &b){
    bint tmp,mod,res;
    int i,lf,rg,mid;
    mod.v[0] = mod.ln = 0;
    for(i=a.ln-1;i>=0;i--){
        mod = mod*base+a.v[i];
        for(lf=0,rg=base-1;lf<rg;){
            mid = (lf+rg+1)/2;
            if(b*mid<=mod)lf=mid;
            else rg = mid-1;
        }
        res.v[i] = lf;
        mod = mod-b*lf;
    }
    res.ln = a.ln;
    while(res.ln>0&&res.v[res.ln-1]==0)res.ln--;
    return mod;
}
int digits(bint &a){
    if(a.ln==0)return 0;
    int l = (a.ln-1)*4;
    for(int t=a.v[a.ln-1];t;++l,t/=10);
    return l;
}
bool read(bint &b,char buf[]){
    if(1!=scanf("%s",buf))return 0;
    int w,u,ln = strlen(buf);
    memset(&b,0,sizeof(bint));
    if('0'==buf[0]&&0==buf[1])return 1;
    for(w=1,u=0;ln;){
        u += (buf[--ln]-'0')*w;
        if(w*10==base){
            b.v[b.ln++] = u;
            u = 0;
            w = 1;
        }else{
            w *=10;
        }
    }
    if(w!=1)b.v[b.ln++]=u;
    return 1;
}
void write(const bint &v){
    int i;
    printf("%d",v.ln==0?0:v.v[v.ln-1]);
    for(i=v.ln-2;i>=0;i--){
        printf("%04d",v.v[i]);
    }
    printf("\n");
}
//
const int MAXN = 10010;
vector<int> g[MAXN],bcc[MAXN];
stack< pair<int,int> > s;
int bcnt,depth,low[MAXN],dfn[MAXN],cut[MAXN],num[MAXN],belong[MAXN],t,n,m;
void init(){
    for(int i=0;i<MAXN;i++)g[i].clear(),bcc[i].clear();
    bcnt = 0;depth = 1;
    memset(cut,0,sizeof(cut));
    memset(dfn,0,sizeof(dfn));
    memset(belong,-1,sizeof(belong));
    memset(num,0,sizeof(num));
}
void tarjan(int fa,int u){
    low[u] = dfn[u] = depth++;int son = 0;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        if(!dfn[v]){
            son++;
            s.push(make_pair(u,v));
            tarjan(u,v);
            low[u] = min(low[u],low[v]);
            if((son>=2&&u==0)||(u!=0&&dfn[u]<=low[v]))cut[u]=1;
            if(dfn[u]<=low[v]){
                int fst=s.top().first,sec=s.top().second;
                while(dfn[fst]>=dfn[v]){
                    if(belong[fst]!=bcnt){
                        bcc[bcnt].push_back(fst);
                        belong[fst] = bcnt;
                    }
                    if(belong[sec]!=bcnt){
                        bcc[bcnt].push_back(sec);
                        belong[sec] = bcnt;
                    }
                    s.pop();
                    fst = s.top().first;sec = s.top().second;
                }
                s.pop();
                if(belong[u]!=bcnt){
                    belong[u] = bcnt;
                    bcc[bcnt].push_back(u);
                }
                if(belong[v]!=bcnt){
                    belong[v] = bcnt;
                    bcc[bcnt].push_back(v);
                }
                bcnt++;
            }
        }else if(v!=fa&&dfn[v]<dfn[u]){
            s.push(make_pair(u,v));
            low[u] = min(low[u],dfn[v]);
        }
    }
}
void make_num(){
    for(int i=0;i<bcnt;i++){
        for(int j=0;j<(int)bcc[i].size();j++){
            if(cut[bcc[i][j]])num[i]++;
        }
    }
}
bint pow2(int p){
    if(p==0)return 1;
    bint a = pow2(p/2);
    if(p%2)return a*a*2;
    else return a*a;
}
void solve(int c){
    bint mul = 1;
    bint mod = pow2(64);
    int ans = 0;
    for(int i=0;i<bcnt;i++){
        if(num[i]==1){
            ans++;
            mul = (mul*(bcc[i].size()-1))%mod;
        }
    }
    printf("Case %d: %d ",c,ans);if(ans)write(mul);else printf("0\n");
}
int main(){
    scanf("%d",&t);
    for(int c=1;c<=t;c++){
        init();scanf("%d%d",&n,&m);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        tarjan(-1,0);
        if(bcnt!=1){
            make_num();
            solve(c);
        }else{//原始图就是双连通时要特殊处理
            printf("Case %d: %d %d\n",c,2,n*(n-1)/2);
        }
    }
    return 0;
}




 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
拓扑排序、割点与割边以及强连通分量是图论中的重要概念和算法。 1. 拓扑排序(Topological Sorting): 拓扑排序是对有向无环图(DAG)进行排序的一种算法。拓扑排序可以得到一个顶点的线性序列,使得对于任意一条有向边(u, v),在序列中顶点u都排在顶点v的前面。拓扑排序常用于表示任务之间的依赖关系,例如在工程项目中确定任务的执行顺序。 2. 割点与割边(Cut Vertex and Cut Edge): 割点是指在无向连通图中,如果移除该顶点以及与该顶点相连的所有边,会导致图不再连通,则该顶点被称为割点。割边是指在无向连通图中,如果移除该边,会导致图不再连通,则该边被称为割边。割点和割边的存在可以影响到图的连通性,因此在网络设计、通信等领域有着重要的应用。 3. 强连通分量(Strongly Connected Component): 强连通分量是指在有向图中,如果对于图中任意两个顶点u和v,存在从u到v和从v到u的路径,那么称u和v在同一个强连通分量中。强连通分量可以将有向图的顶点划分成若干个子集,每个子集内的顶点之间互相可达。强连通分量可以用于分析网络中的关键节点,寻找网络的可靠性,以及在编译器设计中进行代码优化等领域。 这些概念和算法在图论中都有着广泛的应用,并且还有许多相关的算法和扩展。深入理解和掌握这些概念和算法,可以帮助我们更好地理解和解决各种与图相关的问题

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值