【强连通分量】CEOI2012network

题目描述:

给出一个有向图。
对图中某两个点p,q定义p到达q是指:从p出发,不经过相同的点,到达q有且仅有一条路径。
现在保证有一个点R,可以到达所有点。(输入数据保证合法)
第一问:求出每个点能到达的点的个数(包括自身)
第二问:求至少添加多少条边,可以使图中任何一个点都能到达所有点。
(这里的到达,同样满足上面的描述)
并且输出这些边(special judge)


分析

首先,很容易想到把强连通分量缩成点,由于R点的存在,使得缩完点后的图一定为一棵树(只有一个点入度为0,且没有环)。
第一问很简单,在树上遍历一遍就有答案了。
第二问相对就有点分析难度了。
首先,这个图一定只由反祖边和树边构成。
现在我们在缩完点后,假设一个点对(a,b)为了方便起见,我们还假定a为b的祖先。
这样显然从a可以走到b,且有唯一路径,但现在要b能走到a,就必须连返祖边,如果有多条路径,就说明返祖边一定有多条,那么从a到b之间的路径,就会同时出现在两个环中。
另外的,如果a和b没有祖先关系,因为不存在横插边,所以a只能先到a,b的某一个公共祖先处,可以看做把a转移成b的祖先,这样又转化为上面的情况了。
所以我们得出结论,如果存在一条边,同时出现在多个环中,那么就有多条路径存在。

接着就是找出最少的边数了。
很显然,在原图中,每个叶子节点都必须连向它的某个祖先,而且从这个祖先到达这个叶子节点,一定不会经过缩环形成的点(除非从边上擦过去,见图示)
【强连通分量】CEOI2012network_第1张图片
而且,一个点如果有多个儿子,那么只有一个可以连接到它上方,其余都只能连接到它。实现过程并不困难,我的实现方式是记录每个点连向缩点后图的父亲的那条边,在原图中的真实边记录下来,这样就可以方便地完成将一个强连通分量连向它的某个祖先这种操作。

#include
#include
#include
#include
#include
#define MAXN 100010
using namespace std;
int n,m,r;
int dfn[MAXN],low[MAXN],nx[MAXN],siz[MAXN];
pair<int,int> l[5*MAXN];
vector<int> a[MAXN];
stack<int> s;
vectorint,int> >ans;
int sum,cnt,ans1[MAXN],ans2[MAXN];
int fa[MAXN],lfa[MAXN];
//fa表示在缩点后的树中,每个点连向的父亲节点在原图中的点的编号
//lfa表示在缩点后的树中,每个点连向父亲节点的自己的点在原图中的编号
void dfs(int x){
    dfn[x]=++cnt;
    low[x]=dfn[x];
    s.push(x);
    for(int i=0;iif(dfn[a[x][i]]==0){
            dfs(a[x][i]);
            low[x]=min(low[x],low[a[x][i]]);
        }
        else
            if(nx[a[x][i]]==0)
                low[x]=min(low[x],dfn[a[x][i]]);
    }
    if(low[x]==dfn[x]){
        sum++;
        while(s.top()!=x){
            nx[s.top()]=sum;
            siz[sum]++;
            s.pop();
        }
        nx[s.top()]=sum;
        siz[sum]++;
        s.pop();
    }
}
int dfs1(int x,int f){
    ans1[x]=siz[x];
    int flag=0;
    for(int i=0;iif(siz[x]==1&&a[x].size()==1&&f!=-1)
            ans1[x]+=dfs1(a[x][i],f);
        else{
            if(f!=-1&&lfa[x]==fa[a[x][i]]&&flag==0){
                flag=1;
                ans1[x]+=dfs1(a[x][i],f);
            }
            else
                ans1[x]+=dfs1(a[x][i],a[x][i]);
        }
    }
    if((siz[x]==1&&a[x].size()==1)||f==-1||flag==1)
        return ans1[x];
    ans.push_back(make_pair(lfa[x],fa[f]));
    return ans1[x];
}
int main(){
    scanf("%d%d%d",&n,&m,&r);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        a[x].push_back(y);
        l[i]=make_pair(x,y);
    }
    dfs(r);
    /*printf("(%d)\n",sum);
    for(int i=1;i<=n;i++)
        printf("{%d %d}\n",i,nx[i]);*/
    for(int i=1;i<=sum;i++){
        a[i].clear();
    }
    for(int i=1;i<=m;i++){
        x=nx[l[i].first];
        y=nx[l[i].second];
        if(x!=y){
            a[x].push_back(y);
            fa[y]=l[i].first;
            lfa[y]=l[i].second;
        }
    }
    dfs1(sum,-1);
    for(int i=1;i<=n;i++)
        ans2[i]=ans1[nx[i]];
    for(int i=1;i<=n;i++)
        printf("%d ",ans2[i]);
    printf("\n%d",ans.size());
    for(int i=0;iprintf("\n%d %d",ans[i].first,ans[i].second);
}

你可能感兴趣的:(图论)