【算法】【题解】【usaco】 最受欢迎的牛

【例题】
每头牛都有一个梦想:成为一个群体中最受欢迎的名牛!在一个有N(1<=N<=10,000)头牛的牛群中,给你M(1<=M<=50,000)个二元组(A,B),表示A认为B是受欢迎的。既然受欢迎是可传递的,那么如果A认为B受欢迎,B又认为C受欢迎,则A也会认为C是受欢迎的,哪怕这不是十分明确的规定。你的任务是计算被所有其它的牛都喜欢的牛的个数。
Input Format
第一行,两个数,N和M。第2~M+1行,每行两个数,A和B,表示A认为B是受欢迎的。
Output Format
一个数,被其他所有奶牛认为受欢迎的奶牛头数。


1.利用强连通分量缩点

在知道强连通分量之后,我们可以对图的连通性有一个新的认识:在每个连通分量构成的点集合中,每两个点之间都是互相联通的。那么在解关于连通性的问题的时候,可以把这一整个集合当做一个点,即“缩点”。
在例题里,我们要求被其他所有奶牛认为受欢迎的奶牛头数,转换到图算法,即求和其他任意一个顶点都是连通的点的个数(注意是有向图)。又因为在一个连通分量的集合里,任意两个点可以互相到达,所以我们就可以把这个集合作为顶点,把上万的数据量缩减到很小。

2.判断与搜索的方法

求解连通分量以后,面临的下一个问题是,对于所有的连通分量集合,哪一个集合是和其他所有集合连通的(集合里的奶牛不仅要两两互相认为是受欢迎的,其他集合里面的奶牛也要认为他们是受欢迎的)。
结论:

当且仅当只有一个集合,集合中的点的出度和为0(没有出边),这个集合中所有的元素才是满足要求的。(前提是没有被孤立的集合,也就是没有出度入度和为0的集合)

下面是证明:
【算法】【题解】【usaco】 最受欢迎的牛_第1张图片

我们假设四个集合分别都是互相连通的。
我们假设图中仅有黑色的实线边,那么图中就存在三个出度为0的集合,由图可知,没有满足的集合。
那么如果没有出度为0的集合呢?我们只考虑图中(1,3)(3,2)(2,4)(4,1)的边,那么很显然,四个集合其实就是连通的,那么在最初求连通分量的时候他们就会被和为一个集合,即:这种情况是不存在的。
对于任意n个点,要将他们单向连通,最少需要n-1条边(每条边为(u,u+1))。在这种情况下,有一个入度为0,有一个出度为0.那么出度为0的点,必定是其他任意点可以到达的。那么,在这里,只需要其他点都有一条出边,留下一个点的出度为0,那么这个点就是满足要求的。因为这样保证了至少有n-1条边,并且不会有两条边的两个顶点完全相同(否则会连通,如图中(2,3)和(3,2)),那么根据抽屉原理,这些边涉及的集合包括了所有的集合。那么就是一个单向连通的图。由上面的陈述可以知道,出度为0的点可以被任意一个顶点到达。
证明结束。
当然这不是特别严谨的证明,但我不知道严谨的证明在哪,这只是我的个人理解。


那么这道题就很简单了。

#include
#include
#include
#include
using namespace std;

int N, M;
#define maxx 11000
struct Edges{
    int x, y, next;
}E[maxx * 5];   int ans = 0;
int V[maxx];
//
int low[maxx], dfn[maxx], ind = 0;//DFS
int stack[maxx], tails = 0;
bool Jud[maxx];
int SCC[maxx], sum = 0;
//
int OutDegree[maxx];
int printout = 0;

void Putin()
{
    memset(V, -1, sizeof(V));
    memset(Jud, false, sizeof(Jud));
    memset(OutDegree, 0, sizeof(OutDegree));
    //
    cin >> N >> M;
    for(int i = 1; i <= M; i++){
        int xi;    int yi;
        cin >> xi >> yi;
        E[++ans].next = V[xi];
        E[ans].x = xi;    E[ans].y = yi;
        V[xi] = ans;
    }
}

inline void DFS_tatjan(int x)
{
    dfn[x] = low[x] = ++ind;
    stack[++tails] = x;
    Jud[x] = true;
    for(int i = V[x]; i > 0; i = E[i].next)
    {
        int y = E[i].y;
        if(!dfn[y]){
            DFS_tatjan(y);
            low[x] = min(low[x], low[y]);
        }
        else    low[x] = min(low[x], dfn[y]);
    }
    if(low[x] >= dfn[x])
    {
        int k; sum++;
        do{
            k = stack[tails--];
            Jud[k] = false;
            SCC[k] = sum;
        } while (k != x);
    }
}

void Search()
{
    for(int i = 1; i <= M; i++){
        int a = SCC[E[i].x], b = SCC[E[i].y];
        if(a != b)  OutDegree[a]++;
    }
    int num = 0;//出度为0的点集的个数;
    int recall;//记录出度为0的点集的序号;
    for(int i = 1; i <= sum; i++){
        if(OutDegree[i] == 0)   num++, recall = i;
        if(num > 1)    break;
    }
    if(num == 1)
        for(int i = 1; i <= N; i++)
            if(SCC[i] == recall)    printout++;
    return;
}

int main()
{
    Putin();
    for(int i = 1; i <= N; i++)
        if(!dfn[i])    DFS_tatjan(i);
    Search();
    cout << printout << endl;
    return 0;
}

你可能感兴趣的:(【算法】【题解】【usaco】 最受欢迎的牛)