poj 1236 Network of Schools (强连通分量+缩点)

题目大概:

每个学校都可以把软件复制好,交给它名单上的学校。

问题A:把软件复制成几份,然后交给不同的学校,所有学校才能够都有软件。

问题B:添加几条边,能使得这个图变成强连通图。

思路:

找出所有的强连通分量,然后缩点,变成一个新有向无环图,求每个强连通分量的入度和出度。

A:入度是0的点就是复制的最小数量,因为只要给强连通分量一个软件,他就会传到每个点,所以找出没有入口的强连通分量的数量就是要复制的数量(其他强连通分量都可以由这些分量传递过去)。

B:in0为入度为0点的数量,out0出度为0的点的数量,添加的边的数量是=max(in0,out0);这个不好证明,但画一下图,自己理解一下,也是很容易理解的。

 

KO:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))

const int N=105;
vector<int>g[N];
vector<int>rg[N];
vector<int>vs;
bool vis[N];
int cmp[N];
int in[N]={0};
int out[N]={0};
int n,a;

void add_edge(int u,int v)
{
    g[u].pb(v);
    rg[v].pb(u);
}

void dfs(int u)
{
    vis[u]=true;
    for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]);
    vs.pb(u);
}

void rdfs(int u,int k)
{
    vis[u]=true;
    cmp[u]=k;
    for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k);
}

int scc()
{
    mem(vis,false);
    vs.clear();
    for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
    
    mem(vis,false);
    int k=0;
    for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++);
    return k;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        while(cin>>a&&a)add_edge(i,a);
    }
    
    int t=scc();
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<g[i].size();j++)
        if(cmp[i]!=cmp[g[i][j]])out[cmp[i]]++,in[cmp[g[i][j]]]++;
    }
    int in1=0,out1=0;
    for(int i=0;i<t;i++)
    {
        //cout<<in[i]<<' '<<out[i]<<endl;
        if(in[i]==0)in1++;
        if(out[i]==0)out1++;
    }
    
    cout<<in1<<endl;
    if(t==1)cout<<0<<endl;
    else cout<<max(in1,out1)<<endl;
    return 0;
}
View Code

TA:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

const int maxn = 100 + 10;

int N;
int In[maxn], Out[maxn];

/***************************Tarjan算法模板***************************/
vector<int> G[maxn];
int Mark[maxn], Root[maxn], Stack[maxn];                                //时间戳,根(当前分量中时间戳最小的节点),栈
bool Instack[maxn];                                                     //是否在栈中标记
int Ssc[maxn];                                                          //每个节点所在的强连通分量的编号
int Index, Ssc_n, Top;                                                  //搜索时用的时间戳,强连通分量总数,栈顶指针

void Tarjan(int u)                                                      //u 当前搜索到的点
{
    Mark[u] = Root[u] = ++ Index;                                       //每找到一个点,对时间戳和根初始化
    Stack[Top ++] = u;                                                  //压栈
    Instack[u] = true;                                                  //在栈中标记

    int v;

    for(int i= 0; i< G[u].size(); i++)                                  //向下搜索
    {
        v = G[u][i];
        if(Mark[v] == 0)                                                //没到过的点
        {
            Tarjan(v);                                                  //先向下搜索
            if(Root[u] > Root[v]) Root[u] = Root[v];                    //更新根
        }
        else if(Instack[v] && Root[u] > Mark[v]) Root[u] = Mark[v];     //到过的点且点仍在栈中,试着看这个点能不能成为根
    }
/*对当前点的搜索结束*/
    if(Mark[u] == Root[u])                                              //当前点本身时根
    {
        Ssc_n ++;                                                       //更新强连通分量数

        do{                                                             //栈中比它后入栈的元素在以它为根的强连通分量中
            v = Stack[-- Top];
            Instack[v] = false;
            Ssc[v] = Ssc_n;
        }while(v != u);                                                 //直到它自己
    }
}

void SSC()
{
    memset(Mark, 0, sizeof Mark);                                       //初始化时间戳和栈内标记
    memset(Instack, false, sizeof Instack);
    Index = Ssc_n = Top = 0;                                            //初始化时间戳,强连通分量数,栈顶指针

    for(int i= 1; i<= N; i++)                                           //保证图上所有点都访问到
        if(Mark[i] == 0) Tarjan(i);
}
/***************************Tarjan算法模板***************************/

int main()
{
    //freopen("in.txt", "r", stdin);

    scanf("%d", &N);
    for(int i= 1; i<= N; i++)
    {
        int x;
        while(scanf("%d", &x), x)
            G[i].push_back(x);
    }

    SSC();

    if(Ssc_n == 1)                                                      //只有一个强连通分量的情况
    {
        cout << "1\n0\n";
        return 0;
    }

    memset(In, 0, sizeof In);                                           //求每个强连通分量的入度和出度
    memset(Out, 0, sizeof Out);
    for(int u= 1; u<= N; u++)
    {
        for(int i= 0; i< G[u].size(); i++)
        {
            int v = G[u][i];
            if(Ssc[u] != Ssc[v])
                Out[Ssc[u]] ++, In[Ssc[v]] ++;
        }
    }

    int S1 = 0, S2 = 0;                                                 //找入度为0、出度为0的点的数目
    for(int i= 1; i<= Ssc_n; i++)
    {
        if(In[i] == 0) S1 ++;
        if(Out[i] == 0) S2 ++;
    }

    cout << S1 << endl << max(S1, S2) << endl;

    return 0;
}
View Code

 

posted @ 2018-09-13 20:47  shuai_hui  阅读(109)  评论(0编辑  收藏  举报