bzoj2730: [HNOI2012]矿场搭建

题目

题解:

无向图双联通分量
详见注释

标程:

#include<bits/stdc++.h>
using namespace std;
#define M(a) memset(a,0,sizeof(a))
#define v e[i].to
typedef long long ll;
const int N=503;
struct kk{
    int to,ne;
}e[N*2];//双向建边,两倍
int tot,n,m,T,cnt,num,low[N],dfn[N],ans1,vis[N],cut[N],head[N],Case,x,y,tim,i;
ll ans2;
int read(){
    int x=0;char ch;
    do ch=getchar();while(ch<48||ch>57);
    while(ch>=48&&ch<=57)x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x;
}
void add(int x,int y){
    e[++tot].to=y;
    e[tot].ne=head[x];
    head[x]=tot;
}
void tarjan(int u,int fa){
    int child=0;//u的孩子数量
    dfn[u]=low[u]=++tim;
    for (int i=head[u];i;i=e[i].ne)
        if (!dfn[v]){
            child++;
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if (low[v]>=dfn[u]) cut[u]=1;
        }else if (dfn[v]<dfn[u] && v!=fa) low[u]=min(low[u],dfn[v]);
    if (child==1 && !fa) cut[u]=0;//若该点为根节点且只有一个儿子,就不是割点
}
void dfs(int u){
    vis[u]=T;cnt++;
    for (int i=head[u];i;i=e[i].ne){
        if (vis[v]!=T && cut[v]) num++,vis[v]=T;
//刚开始把vis[v]!=T写成!vis[v],后来发现一个割顶可能会在多个联通块中出现
        if (!vis[v] && !cut[v]) dfs(v);
    }
}
int main(){
    do{
        m=read();
        if (!m) return 0;
        M(vis);M(cut);M(dfn);M(head);//偷了个懒,low不用清空
        n=ans1=T=tot=tim=0;ans2=1;//清空,有多组数据
        for (i=1;i<=m;i++) x=read(),y=read(),add(x,y),add(y,x),n=max(n,max(x,y));
        for (i=1;i<=n;i++)
            if (!dfn[i]) tarjan(i,0);
        for (i=1;i<=n;i++)
            if (!vis[i] && !cut[i]){
                T++,cnt=num=0;
//cnt为当前联通块中总的点的个数,num为割点个数
                dfs(i);
                if (!num) ans1+=2,ans2*=cnt*(cnt-1)/2;
                //内部无割点,则任意选两个出口
                if (num==1) ans1++,ans2*=cnt;
     //内部有一个割点,就任意选一个非割顶的点当作出口
            }
        printf("Case %d: %d %lld\n",++Case,ans1,ans2);
    }while (1);
}

猜你喜欢

转载自blog.csdn.net/xumingyang0/article/details/80046150
今日推荐