Proving Equivalences UVALive 4287 图的强连通分量

Consider the following exercise, found in a generic linear algebra textb o ok.

LetAbe ann×nmatrix. Prove that the following statements are equivalent:
(a) Ais invertible.
(b) Ax=bhas exactly one solution for everyn×1 matrixb.
(c) Ax=bis consistent for everyn×1 matrixb.
(d) Ax= 0 has only the trivial solutionx= 0.
The typical way to solve such an exercise is to show a series of implications. For instance, one can
proceed by showing that (a) implies (b), that (b) implies (c), that (c) implies (d), and nally that (d)
implies (a). These four implications show that the four statements are equivalent.
Another way would be to show that (a) is equivalent to (b) (by proving that (a) implies (b) and
that (b) implies (a)), that (b) is equivalent to (c), and that (c) is equivalent to (d). However, this way
requires proving six implications, which is clearly a lot more work than just proving four implications!
I have been given some similar tasks, and have already started proving some implications. Now I
wonder, how many more implications do I have to prove? Can you help me determine this?
Input
On the rst line one positive number: the number of testcases, at most 100. After that per testcase:
One line containing two integers n (1 n 20000) andm(0m50000): the number of
statements and the number of implications that have already been proved.
m lines with two integerss1ands2(1s1, s2nands1̸=s2) each, indicating that it has been
proved that statement s 1 implies statement s 2.
Output
Per testcase:
One line with the minimum number of additional implications that need to be proved in order to
prove that all statements are equivalent.
Sample Input
2
4 0
3 2
1 2
1 3
Sample Output
42

题目大意:每个问题对应一个结点,两个问题已经有implications 表示两个结点之间有了一条边;把所有的问题看成一个图,要证明各个问题等价,就是要使整个图成为一个强连通分量。需要求解最少还需要几条边?

解题思路:求出题中的强连通分量,然后将每个强连通分量看成一个“点”;然后统计每个“点”的ID和OD,如果某个“点”的ID=0,则idcnt+1,如果某个点的OD=0,则odcnt+1,最后max(idcnt,odcnt)就是结果。(不理解ID,OD,连通分量这些名词的同学,请参考《算法导论》这本书)

算法知识:经典的强连通分量入门题目,tarjan算法+缩点 或者 kosaraju算法+缩点

学习说明:求解强连通分量常用的算法有tarjan算法、kosaraju算法。kosaraju算法我是在《算法导论》书上学的,tarjan算法是在网上学的,给出我看的几个链接:

http://blog.sina.com.cn/s/blog_69a101130100k1cm.html

https://www.byvoid.com/blog/scc-tarjan/

http://baike.baidu.com/link?url=pFZBstBZdR5ERYMExvpdHAHqBlt2RK0ZbESX_53Ui456lBW6eR7hZSlJ1AseVnrFlVzGC2ANGAuVKlW04r7ukq

Tarjan算法的伪代码如下:

tarjan(u)
{
    DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
    Stack.push(u)                              // 将节点u压入栈中
    for each (u, v) in E                       // 枚举每一条边
        if (v is not visted)               // 如果节点v未被访问过
            tarjan(v)                  // 继续向下找
            Low[u] = min(Low[u], Low[v])
        else if (v in S)                   // 如果节点v还在栈内
            Low[u] = min(Low[u], DFN[v])
    if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
        repeat
            v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
            print v
        until (u== v)
}

参考源代码:

tarjan算法:http://blog.csdn.net/hcbbt/article/details/38301669

kosaraju算法:http://blog.csdn.net/lenleaves/article/details/8982673

我写的源代码:

///对图可以使用结构体存储,也可以使用vector存储,采用的邻接链表的思路
///这里使用vector,方便我熟悉
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#define N 20010
#define WHITE 0
#define GRAY 1
#define BLACK 2
using namespace std;
vector<int> vec[N];///存储每一条边,vec数组的下标是结点的编号
stack<int> node;

int n=0,m=0,t=0;
int scc[N];///存储每个结点所属的强连通分量,数组下标是结点编号,数组的值是结点所属的强连通分量的值
int color[N];///存储每个结点的颜色,白色表示没有访问,灰色表示还在stack中,黑色表示已经生成了强连通分量
int low[N];///每个结点的low值
int dfn[N];///每个结点的dfn值
int sccid[N];///存储每个强连通分量的入度
int sccod[N];///存储每个SCC的出度
int ans=0;///最后的结果
int lowcnt=0,dfncnt=0;
int scccnt=0;///强连通分量的编号,从0开始

int tarjan(int i){///i是结点编号
    int temp=0;
    node.push(i);
    color[i]=GRAY;
    lowcnt++;dfncnt++;
    low[i]=lowcnt;dfn[i]=dfncnt;///从1开始编号
    for(unsigned int j=0;j<vec[i].size();j++){
        if(color[vec[i][j]]==WHITE){
            tarjan(vec[i][j]);
            low[i]=min(low[i],low[vec[i][j]]);
        }
        else if(color[vec[i][j]]==GRAY){
            low[i]=min(low[i],dfn[vec[i][j]]);
        }
    }
    if(low[i]==dfn[i]){///产生一个SCC(强连通分量)
        do{
            temp=node.top();
            scc[temp]=scccnt;///存储该节点对应的SCC
            node.pop();
            color[temp]=BLACK;///出栈后为black,表示已经为该节点分配SCC
        }while(temp!=i);
        scccnt++;///SCC的编号值+1
    }
    return 0;
}
int cmscc(){
    for(int i=1;i<=n;i++){
        if(color[i]==WHITE){
            tarjan(i);
        }
    }
    return 0;
}
int findiod(){
    int idcnt=0,odcnt=0;
    for(int i=1;i<=n;i++){
        for(unsigned int j=0;j<vec[i].size();j++){
            if(scc[i]!=scc[vec[i][j]]){///如果两个相邻结点不属于一个SCC
                sccod[scc[i]]++;///i结点的SCC出度加1
                sccid[scc[vec[i][j]]]++;///j结点的scc入度加1
            }
        }
    }
    if(scccnt>1)///scccnt=1的时候,只有一个连通分量,输出必然是0;
    for(int i=0;i<scccnt;i++){
        if(sccid[i]==0) idcnt++;
        if(sccod[i]==0) odcnt++;
    }
    ans=max(idcnt,odcnt);
    return 0;
}
int read(){
    int nod1=0,nod2=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        vec[i].clear();
    }
    for(int i=1;i<=m;i++){
        cin>>nod1>>nod2;
        vec[nod1].push_back(nod2);///0下标的vector不使用
    }
    return 0;
}
int main()
{

    cin>>t;
    while(t--)
    {
        memset(scc,0,sizeof(scc));
        memset(color,0,sizeof(color));
        memset(low,0,sizeof(low));
        memset(dfn,0,sizeof(dfn));
        memset(sccid,0,sizeof(sccid));
        memset(sccod,0,sizeof(sccod));
        ans=0;lowcnt=0;dfncnt=0;scccnt=0;
        read();
        cmscc();///计算强连通分量
        findiod();///计算出度入度
        cout<<ans<<endl;
    }
    return 0;
}


 
 













你可能感兴趣的:(Proving Equivalences UVALive 4287 图的强连通分量)