有向图强连通分量,练习1:hdu 3072 + hdu 4635 + poj 1236

4-26 -> 4-27
强连通分量的概念上篇已经讲得很详细了,今天做题我给模板上加了一个数组 belong , belong[i] 表示点i 存在于哪一个强连通分量之中
//
先来看第一道题:
hdu3072 : http://acm.hdu.edu.cn/showproblem.php?pid=3072
题意弯弯绕绕的, 从0 这个点传送信息到每一个点, 意味着 0 这点可到达每一个其他点,每一条边有一定的费用,但是在强连通分量里面所有边是没有费用的,求完成这个连通工程的最小的费用

2016-5-10回顾:
T1:其实只要牢记一点,强连通分量最重要的点之一就是 每一个分量的出边和入边

: 一开始想歪了,题意也没有读清楚,重点是0 这个点要能 到达所有其他点, 首先我们可以跑一遍tarjan,把图中的所有强连通分量都跑出来,这些点不管相互有多少费用都变为0,然后将这个强连通分量看为一个点, 我们需要得到最小的权值和。 其实很简单,但是我自己还没有那个思维, 记住一点:对于这种强连通分量,他的入边,出边是很重要的: 比如,对于这一道题,我们只要找出每一个强连通分量的最小入边(起点是另外一个分量),然后除了0 这一点的强连通分量,我们把其他每一个强连通的最小入边加起来即可:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
using namespace std;
#define M 50005 //题目中可能的最大点数
int STACK[M],top;          //Tarjan 算法中的栈
bool InStack[M];             //检查是否在栈中
int DFN[M];                  //深度优先搜索访问次序
int Low[M];                  //能追溯到的最早的次序
int Comnumber;        //有向图强连通分量个数
int Index;                 //索引号
vector <int > Edge[M];        //邻接表表示
vector <int> Com[M];   //获得强连通分量结果
int belong[M]; //给每一个标记他的强连通分量编号
int in[M]; //记录每一个连通分量的入边
int sta[100005],to[100005],c[100005];  //记录每一条边,然后可以用On 找出每一个强连通分量的最小入边,或出边
void Tarjan(int i)
{
    int j;
    DFN[i]=Low[i]=Index++;
    InStack[i]=true;
    STACK[++top]=i;
    for (int e=0;e<Edge[i].size();e++){
        j=Edge[i][e];
        if (DFN[j]==-1){  //没有访问过
            Tarjan(j);
            Low[i]=min(Low[i],Low[j]);
        }
        else if (InStack[j])
            Low[i]=min(Low[i],DFN[j]);  //这一层的递归里面。Low[j]是还没有更新的,讲道理这里写DFN和LOW j没区别
    }
    if (DFN[i]==Low[i])
    {
        Comnumber++;
        do
        {
            j=STACK[top--];
            InStack[j]=false;
            belong[j]=Comnumber;
        }
        while (j!=i);
    }
}

void solve(int N)     //此图中点的个数,注意是0-indexed!
{
    Comnumber=0;
    top=0;
    Index=1;
    memset(STACK,-1,sizeof(STACK));
    memset(InStack,0,sizeof(InStack));
    memset(DFN,-1,sizeof(DFN));
    memset(Low,-1,sizeof(Low));
    for(int i=1;i<=N;i++)
        if(DFN[i]==-1)
            Tarjan(i);
}
//想法:
//先对强连通分量进行缩点统计点个数
int main(){
    int n,m;
    //freopen("1.txt","r",stdin);
    while(~scanf("%d %d",&n,&m)){
        for(int i=1;i<=M;i++){
             Edge[i].clear();
        }
        for(int i=1;i<=m;i++){
            scanf("%d %d %d",&sta[i],&to[i],&c[i]);
            sta[i]++;
            to[i]++;
            Edge[sta[i]].push_back(to[i]);
        }
        solve(n);
        for(int i=1;i<=Comnumber;i++){ //改动
            in[i]=100001;
        }
        for(int i=1;i<=m;i++){
            int u=sta[i],v=to[i],C=c[i];
            if(belong[u]!=belong[v]){
                in[belong[v]]=min(in[belong[v]],C);
            }
        }
        int ans=0;
        for(int i=1;i<=Comnumber && i!=belong[1] ;i++){
            if(in[i]!=100001)
                ans+=in[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}

第二道题:
hdu 4635
http://acm.hdu.edu.cn/showproblem.php?pid=4635
题意:
题意简洁:对于一个有向简单图(没重边,没自环),问最多可以加多少边 使得整个图任然是一个简单有向图,但是整个图不是强连通的!

2016-5-10回顾:
T2:第一步对已经是强连通分量的块 缩为一个点(并且可以加一些边),对于剩下的图必然存在一个入度为0 的“点”或者出度为0 ,否则必然是强连通啊, 然后这个点不动,其他点组成完全图,应该就是最多的边吧, 讲道理这样写也不复杂很快可以A吧。 还是年轻
哦哦,看完正解发现自己还是太年轻了,忽略了一个地方:“对于我们隔离的点”必须是包含点数最少的块,是吧,因为这样在整个的完全图里面这样删除的边就是最少的,然后还要考虑初始给定的边是不能够删除的,所以应该找 “初始图中”的 “出度为0或者入度为0” 且“ 包含点最少” 的强连通分块,理论上
这样就是正解了

这个题能保留对于这些题的想法其实是很简单的: 对于一开始给的图(一开始给了m条边),肯定包含一些强连通分量,我们可以把强连通分量看成点。

然后对于这个图,我们如果要保持整体不是强连通,我们就只需要排除一个 点(强连通分量) 即可: 即这个点没有 入边,或者没有出边,满足一个条件 整个图就不是强连通图了。

如果有n个点 ,那么对于完全图 就有 n*(n-1)条边,
一开始给了m条边,所以ans=n*(n-1) -m;
那么我们如何找强连通分量呢? 自然我们找到那个包含 点最少的强连通分量,把他的入边 或者 出边都删掉即可以(这两个其实边数是相等的)。
所以ans -= v*(n-v);

仔细想想这样真的对吗? 是不是想错的地方,在这个地方wa了一发, 没错 我们没有意识到一开始给的m条边有可能对于 这个包含最小点的分量 既有入边也有出边,这个时候这个强连通分量是无论如何都删不掉的。
所以我们必须找一开始给出的边中 入边或者 出边为0的强连通分量中 包含点最小的 分量。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
using namespace std;
#define M 100005 //题目中可能的最大点数
int STACK[M],top;          //Tarjan 算法中的栈
bool InStack[M];             //检查是否在栈中
int DFN[M];                  //深度优先搜索访问次序
int Low[M];                  //能追溯到的最早的次序
int Comnumber;        //有向图强连通分量个数
int Index;                 //索引号
vector <int > Edge[M];        //邻接表表示
vector <int > Com[M];   //获得强连通分量结果
int in[M];   
int out[M];
int num[M]; //给每一个标记他的强连通分量编号 = belong
void Tarjan(int i)
{
    int j;
    DFN[i]=Low[i]=Index++;
    InStack[i]=true;
    STACK[++top]=i;
    for (int e=0;e<Edge[i].size();e++){
        j=Edge[i][e];
        if (DFN[j]==-1){  //没有访问过
            Tarjan(j);
            Low[i]=min(Low[i],Low[j]);
        }
        else if (InStack[j])
            Low[i]=min(Low[i],DFN[j]);  //这一层的递归里面。Low[j]是还没有更新的,讲道理这里写DFN和LOW j没区别
    }
    if (DFN[i]==Low[i])
    {
        //cout<<"TT "<<i<<" "<<Low[i]<<endl;
        Comnumber++;
        do
        {
            j=STACK[top--];
            InStack[j]=false;
            num[j]=Comnumber;
            Com[Comnumber].push_back(j);
        }
        while (j!=i);
    }
}

void solve(int N)     //此图中点的个数,注意是0-indexed!
{
    Comnumber=0;
    top=0;
    Index=1;
    memset(STACK,-1,sizeof(STACK));
    memset(InStack,0,sizeof(InStack));
    memset(DFN,-1,sizeof(DFN));
    memset(Low,-1,sizeof(Low));
    memset(num,-1,sizeof(num));
    memset(out,0,sizeof(out));
    memset(in,0,sizeof(in));
    for(int i=1;i<=N;i++)
        if(DFN[i]==-1)
            Tarjan(i);
}
int main(){
    int t;
    int n,m;
    //freopen("1.txt","r",stdin);
    scanf("%d",&t);
    for(int k=1;k<=t;k++){
        for(int i=1;i<=M;i++)
            Edge[i].clear();
        for(int i=1;i<=M;i++)
               Com[i].clear();
        printf("Case %d: ",k);
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++){
            int sta,to;
            cin>>sta>>to;
            Edge[sta].push_back(to);
        }
        solve(n);
// printf("Comnumber=%d\n",Comnumber);
        if(Comnumber==1){
            printf("-1\n");
            continue;
        }
        __int64 minn=100005;

         for(int i=1;i<=n;i++)
               for(int j=0;j<Edge[i].size();j++)
                    if(num[i]!=num[Edge[i][j]])   //如果这两点 不是在同一个连通分量里面
                    {
                        out[num[i]]++;
                        in[num[Edge[i][j]]]++;
                    }

         for(int i=1;i<=Comnumber;i++)
         {
             if(in[i]==0||out[i]==0)
             {
                  if( (__int64)Com[i].size() < minn)
                      minn=(__int64)Com[i].size();
             }
         }
        __int64 ans=(__int64)n*(n-1);
        ans-=(__int64)m;
        ans-=(__int64)( (n-minn)*minn);
        printf("%I64d\n",ans);
    }
    return 0;
}

poj 2366
这个问题包含两个很经典的问题:
1.对于一个有向图 , 最少需要发几份文件,才能让文件传遍每一个点。
就是找入度为0 的连通块有几个,有几个就发几份

2.对于一个有向图,最少加几条边让他成为一个强连通图(彼此可达)
第二个问题解法:
如果这个图本身是一个强连通图,那么不要加。
否则,添加边是 max(入度为0 的块 数量, 出度为 0 的块的数量) 。 为什么取最大值就是需要加的边呢?
在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少
加边的方法:
要为每个入度为0的点添加入边,为每个出度为0的点添加出边
假定有 n 个入度为0的点,m个出度为0的点,如何加边?
把所有入度为0的点编号 0,1,2,3,4 ….N -1
每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个出度0点,
这需要加n条边
若 m <= n,则
加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边
若 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。
所以,max(m,n)就是第二个问题的解

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
using namespace std;
#define M 110 //题目中可能的最大点数
int STACK[M],top;          //Tarjan 算法中的栈
bool InStack[M];             //检查是否在栈中
int DFN[M];                  //深度优先搜索访问次序
int Low[M];                  //能追溯到的最早的次序
int Comnumber;        //有向图强连通分量个数
int Index=0;                 //索引号
int belong[M]; //给每一个标记他的强连通分量编号
vector<int> Edge[M];        //邻接表表示
vector<int> Com[M];   //获得强连通分量结果
int in[M];
int out[M];   //存每一个强连通分量的出度,入度
void Tarjan(int i)
{
    int j;
    DFN[i]=Low[i]=Index++;
    InStack[i]=true;
    STACK[++top]=i;
    for (int e=0;e<Edge[i].size();e++){
        j=Edge[i][e];
        if (DFN[j]==-1){
            Tarjan(j);
            Low[i]=min(Low[i],Low[j]);
        }
        else if (InStack[j])
            Low[i]=min(Low[i],DFN[j]);  //这一层的递归里面。Low[j]是还没有更新的,讲道理这里写DFN和LOW j没区别
    }
    if (DFN[i]==Low[i])
    {
        //cout<<"TT "<<i<<" "<<Low[i]<<endl;
        Comnumber++;
        do
        {
            j=STACK[top--];
            InStack[j]=false;
            Com[Comnumber].push_back(j);
            belong[j]=Comnumber;
        }
        while (j!=i);
    }
}

void solve(int N)     //此图中点的个数,注意是0-indexed!
{
    Comnumber=0;
    top=0;
    Index=1;
    memset(STACK,-1,sizeof(STACK));
    memset(InStack,0,sizeof(InStack));
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    memset(DFN,-1,sizeof(DFN));
    memset(Low,-1,sizeof(Low));
    for(int i=1;i<=N;i++)
        if(DFN[i]==-1)
            Tarjan(i);
}
void init(){
    for(int i=1;i<=M;i++){
        Edge[i].clear();
        Com[i].clear();
    }
}
/* 此算法正常工作的基础是图是1-indexed的。 */
int main(){
    int n;
    freopen("1.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++){
            int tem;
            while(~scanf("%d",&tem) &&tem){
                Edge[i].push_back(tem);
            }
        }
        solve(n);
        for(int i=1;i<=n;i++){
            int len=(int)Edge[i].size();
            for(int j=0;j<len;j++){
                int u=i,v=Edge[i][j];
                if(belong[u] != belong[v]){  //妈蛋 ,居然忘记写这个
                    in[ belong[v] ]++;
                    out[belong[u] ]++;
                }
            }
        }
        int ans1=0;
        int ans2=0;
        if(Comnumber==1){
            printf("1\n0\n");
            continue;
        }
        for(int i=1;i<=Comnumber;i++){
            if(in[i]==0){
                ans1++;
            }
            if(out[i]==0)
                ans2++;
        }
        ans2=max(ans1,ans2);
        printf("%d\n",ans1);
        printf("%d\n",ans2);
    }
}


你可能感兴趣的:(有向图强连通分量,练习1:hdu 3072 + hdu 4635 + poj 1236)