题目大意:加最少的边使有向图变成强联通图,读懂题意就好办,网赛时通过率很高。
照以前hdu2767、3836的代码改的。(几乎没怎么变…)
#include <iostream>
#include <vector>
#include <stack>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 20000+10;
vector<int>grap[MAXN]; //稀疏图,用邻接表表示图
stack<int>S;
int low[MAXN]; //low[u] 为u或u的子树能够追溯到的最早的栈中节点的次序编号
int num[MAXN]; //num[u] 为u搜索的次序编号(时间戳)
int visited[MAXN]; //标记是否已经被搜索过
int instack[MAXN]; //标记是否在栈中
int index1;
int cnt_scc; //记录总共将图缩成多少个点
int belong[MAXN]; //belong[i] = j 表示原图的点i缩点后为点j
int min(int a, int b)
{
return a < b ? a : b;
}
int max(int a, int b)
{
return a > b ? a : b;
}
void init(int n)
{
for(int i=0; i<=n; i++)
{
grap[i].clear();
}
while(!S.empty())
{
S.pop();
}
memset(instack,0,sizeof(instack));
memset(visited,0,sizeof(visited));
memset(low,-1,sizeof(low));
memset(num,-1,sizeof(num));
memset(belong,-1,sizeof(belong));
index1 = 0;
cnt_scc = 0;
}
//找出连通分支,并缩点
void tarjan(int v)
{
low[v] = num[v] = ++index1;
S.push(v);
instack[v] = 1;
visited[v] = 1;
for(int i=0; i<grap[v].size(); i++)
{
int w = grap[v][i];
if(!visited[w])
{
tarjan(w);
low[v] = min(low[v],low[w]); //v或v的子树能够追溯到的最早的栈中节点的次序编号
}
else if(instack[w]) //(v,w)为后向边
{
low[v] = min(low[v],num[w]);
}
}
int u;
if(low[v] == num[v]) //满足强连通分支条件,进行缩点
{
++cnt_scc;
do
{
u = S.top();
belong[u] = cnt_scc; //缩点
S.pop();
instack[u] = 0; //出栈解除标记
}while(u != v);
}
}
int main()
{
int n,m;
int a,b;
int indegree[MAXN];
int outdegree[MAXN];
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init(n);
memset(indegree,0,sizeof(indegree));
memset(outdegree,0,sizeof(outdegree));
while(m--)
{
cin>>a>>b;
grap[a].push_back(b);
}
for(int i=1; i<=n; i++)
{
if(!visited[i])
{
tarjan(i);
}
}
//求缩点后,各个顶点的出度和入度
for(int i=1; i<=n; i++)
{
for(int j=0; j<grap[i].size(); j++)
{
int k = grap[i][j];
if(belong[i] != belong[k])
{
indegree[belong[k]]++;
outdegree[belong[i]]++;
}
}
}
a = b = 0;
for(int i=1; i<=cnt_scc; i++)
{
if(!indegree[i])
a++;
if(!outdegree[i])
b++;
}
if(cnt_scc == 1) //如果图已经为强连通图
cout<<"0"<<endl;
else
cout<<max(a,b)<<endl;
}
return 0;
}