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

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

题目来源,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;
}




你可能感兴趣的:(问题)