LA 4287 Proving Equivalences(tarjan+强连通缩点)

题意:n个命题,已经做了m次推导,问为了证明这n个命题全部等价,至少还需要几次推导。

分析:n个命题相当于n个顶点,m次推导相当于m条有向边。全部等价相当于图强连通。

求出图中的强连通分量,若只有一个,即图本身就是强连通,则无需推导,答案为0。

否则进行缩点,得到一个DAG图,分别统计该图中点的入度和出度总数a和b,答案就是max(a,b)。


#include<bits/stdc++.h>
using namespace std;
#define maxn 20005
vector<int> G[maxn];
int pre[maxn],low[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;

void dfs(int u){
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0;i<G[u].size();++i){
        int v=G[u][i];
        if(!pre[v]){
            dfs(v);
            low[u]=min(low[u],low[v]);//用后代的low函数更新自身
        }
        else if(!sccno[v]){
            low[u]=min(low[u],pre[v]);//用反向边更新
        }
    }
    if(low[u]==pre[u]){
        ++scc_cnt;
        for(;;){
            int x=S.top();S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}

void find_scc(int n){
    dfs_clock=scc_cnt=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0;i<n;++i)
        if(!pre[i]) dfs(i);
}
int in[maxn],out[maxn];
int main()
{
    int i,t,a,b,j,n,m;
    cin>>t;
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(i=0;i<n;++i) G[i].clear();
        for(i=1;i<=m;++i){
            scanf("%d%d",&a,&b);
            --a,--b;
            G[a].push_back(b);
        }
        find_scc(n);
        if(scc_cnt==1) {puts("0");continue;}
        for(i=1;i<=scc_cnt;++i) in[i]=out[i]=0;
        for(i=0;i<n;++i){
            for(j=0;j<G[i].size();++j)
            {
                int v=G[i][j];
                if(sccno[v]!=sccno[i]) out[sccno[i]]=in[sccno[v]]=1;
            }
        }
        a=b=0;
        for(i=1;i<=scc_cnt;++i)
        {
            if(!in[i])++a;
            if(!out[i]) ++b;
        }
        printf("%d\n",max(a,b));
    }
    return 0;
}


你可能感兴趣的:(LA 4287 Proving Equivalences(tarjan+强连通缩点))