Introduction to Strongly Connected Components-Trajan Algorithms

Keywords: less

Today we learned about strongly connected components.
[Reference Blog]
If you find some of what I'm talking about difficult to understand or problematic (please leave a message), take a look at the blogs of some of the big guys I've learned from.
Port 1 Portal 2
[Knowledge Reserve]
First, we need to have some concepts about several definitions:
Strongly Connected: Two Points in Directed Graphs Can Reach Each other
Strongly Connected Graphs: Any Two Points in Directed Graphs are Strongly Connected
Strongly Connected Components: A digraph whose subgraph is the largest subgraph of a strongly connected graph is called a strongly connected component.

Generally speaking, any two points in a strongly connected component can reach each other, that is to say, there is at least one path that can access all points and return to the starting point (which can be repeated points), just like a ring, so we use DFS with special algorithm to solve the problem of finding connected components.

[Introduction to Algorithms]
We usually use Trajan algorithm to solve related problems.
Just like the simple analysis above, the so-called strongly connected component is that we want to find a path that can go back to the starting point and go through as many points as possible. How do we judge that we are back to the starting point (or the point we have visited)? We need to mark the state of each point with an array to facilitate our judgment.
Two key arrays are introduced here:
DFN [MAXN] DFN [MAXN] DFN [MAXN] is used to mark the order/time when DFS accesses this point
LOW[MAXN]LOW[MAXN]LOW[MAXN] LOW [MAXN] is used to store the earliest access time in the subtree (the point from which access can be made), which means that access to the previous point, for the former point formed a path from its own point of departure and back to its own, that is, a strongly connected component, initialized as DFN, that is, itself. Access time of ____________

If the value of LOW does not change after DFS changes the value of LOW for a point, it is still equal to DFN, which means that starting from this point, it is impossible to go back to the earlier point, the earliest is itself, that is to say, it must not be in the connected component of the previous point (otherwise, it will surely be able to access the previous point, and before). The LOW of each point is relatively small, so the point that the LOW has not changed is the root node of a connected component (sadly, only one of its nodes, but it is possible that its sub-nodes will visit him to form a connected component, but in any case he is the root node). We might as well use a stack to save the order of access. Then all the points he visits after him must be the points in his connected component, and all of them pop up. (If not, the latter point must be a strongly connected component, so it must pop up earlier.) Perhaps a little confused, first there is a concept and then look at the code (note that it is recursive, that is, access after processing before returning to deal with), and then come back to carefully look at this sentence may have a better understanding.

[Algorithmic Implementation]
For each point, we search deeply, timestamp each point (assign values to DFN and LOW) and then directly access each point that has not been visited, and change the value of LOW to the smallest of all subsequent access points. If he encounters a visited point, if he is not in the stack, it means that he has nothing to do with the strongly connected component (the point has been visited elsewhere, can not reach this point, can not form a loop, otherwise the point must be in the stack). It's important to understand that the stack holds points that can be accessed from visited points (other unrelated points have popped up), so if the visited point is in the queue (happier, indicating that a ring has been formed), if the minimum timestamp of this point is less than that, save the LOW. Then the value will be traced back (the point that may be accessed is in another small strongly connected component, so I think it should be compared with LOW, but I see other blogs are compared with DFN, think about it carefully, it is also possible to save DFN, but I still think the value of LOW can be compared with LOW. It is more elegant to represent whether there exists in the same strongly connected component. emm, if you think you can't understand, look down first, and don't care about the details. Finally, DFS will come back to see if the value of LOW has changed. If there is no change, all the points behind it will form a strong connected component, and then all of them will pop up (because the nodes behind him can not access the nodes in front of him, the earliest one is him, so they have no influence on the ones in front of them, pop up). Just go, the same thing, and he has nothing to do with the early pop-up, so don't worry about the latter does not have anything to do with the point.

Look at the code to understand it, where do you think you can not understand the above analysis

void Trajan(int x)
{
    int v,tmp;

    DFN[x]=LOW[x]=++idx;	//Give a timestamp
    stack[++top]=x; 
    vis[x]=true;
    for(int i=head[x];i;i=Edge[i].next)
    {
        v=Edge[i].v;
        if(!DFN[v])
        {
            Trajan(v);
            if(LOW[v]<LOW[x]) LOW[x]=LOW[v];
        }
        else if(vis[v] && LOW[v]<LOW[x])
        {
            LOW[x]=LOW[v];
        }
    }
    if(DFN[x]==LOW[x])
    {
        cnt++;
        do
        {
            tmp=stack[top--];
            vis[tmp]=false;
            color[tmp]=cnt;
        }while (tmp!=x);
    }
}

[Example Title]
Popular Cows

Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.

Input

* Line 1: Two space-separated integers, N and M

* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.

Output

*Line 1: A single integer that is the number of cows who are considered popular by every other cow.

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

Main idea of the title:

There's a herd of cattle. They worship each other and find out how many cattle all worship.

Input: Number of cattle n worship relation m, then M line A,B, indicating A worship B

Output: Number of cattle worshipped by all cattle

[Analysis of sample cases]
The relationship of worship is transformed into a digraph. The cattle in a strongly connected component must worship each other. We regard all cattle in a strongly connected component as a point (dyeing). Different points form a graph again. The only number of cattle in a point with a degree of 0 in this graph is the answer.
Because the point of 0 out must be worshipped by the cattle in other points, if the point of 0 out is greater than 1, the cattle in the two points of 0 out can not be worshipped, so it must not be worshipped by all cattle, so if the point of 0 out is more than one answer is 0, otherwise it is the cattle in the point of 0 out. Number of points with a degree of 0 must be greater than or equal to 1. If there are no points with a degree of 0, they form a ring, and we just said that they are different strongly connected components. If there is only one point with a degree of 0, it also conforms to a point with a degree of 0.

[AC Code]

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<climits>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;

typedef long long ll;
const int MAXN=1e4+5;
const int MAXM=5e4+5;
struct node
{
    int v,next;
}Edge[MAXM];
int head[MAXN],tot;
int DFN[MAXN],LOW[MAXN];
int color[MAXN],cnt;
bool vis[MAXN];
int idx;
int stack[MAXN],top;
int OutDegree[MAXN];
int n,m;

void init()
{
    memset(head,0,sizeof(head)); tot=0;
    idx=0; memset(vis,0,sizeof(vis));
    memset(DFN,0,sizeof(DFN));
    memset(color,0,sizeof(color));
    cnt=0; top=0;
    memset(OutDegree,0,sizeof(OutDegree));
}

void read()
{
    int u,v;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&u,&v);
        tot++;
        Edge[tot].v=v; Edge[tot].next=head[u];
        head[u]=tot;
    }
}

void Trajan(int x)
{
    int v,tmp;

    DFN[x]=LOW[x]=++idx;
    stack[++top]=x; vis[x]=true;
    for(int i=head[x];i;i=Edge[i].next)
    {
        v=Edge[i].v;
        if(!DFN[v])
        {
            Trajan(v);
            if(LOW[v]<LOW[x]) LOW[x]=LOW[v];
        }
        else if(vis[v] && LOW[v]<LOW[x])
        {
            LOW[x]=LOW[v];
        }
    }
    if(DFN[x]==LOW[x])
    {
        cnt++;
        do
        {
            tmp=stack[top--];
            vis[tmp]=false;
            color[tmp]=cnt;
        }while (tmp!=x);
    }
}

void solve()
{
    int v,mark,num,ans;

    for(int i=1;i<=n;i++)
    {
        if(!DFN[i])
        Trajan(i);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=head[i];j;j=Edge[j].next)
        {
            v=Edge[j].v;
            if(color[i]!=color[v])
            OutDegree[color[i]]++;
        }
    }
    num=0; mark=-1;
    for(int i=1;i<=cnt;i++)
    {
        if(!OutDegree[i])
        {
            num++; mark=i;
        }
    }
    ans=0;
    if(num!=1)
    {
        printf("0\n");
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            if(color[i]==mark)
            {
                ans++;
            }
        }
        printf("%d\n",ans);
    }
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        read();
        solve();
    }

    return 0;
}

Posted by Mightywayne on Thu, 15 Aug 2019 23:00:48 -0700