题意: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;
}