hdu2767
题意
给n个点和m条有向边,求最少还需多少条边n个点构成强联通图。
思路
tarjan先处理出每一个强连通分量。
如果强连通分量的个数为1,则答案为0。
如果强连通分量的个数大于1,将每个强连通分量看成一个点,每个强连通分量之间可能存在边,a为没有出度的强连通分量的个数,b为没有入度的强连通分量的个数。则答案为max(a,b)(动手画下很容易得到)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 +7;
typedef long long ll;
int n, m, scc, low[maxn], dfn[maxn], vis[maxn], tot;//scc连通分量数
vector<int> g[maxn];//存图
stack<int> st;//辅助栈
int id[maxn], p[maxn], in[maxn], out[maxn];
void tarjan(int u)
{
dfn[u] = low[u] = ++tot;
st.push(u);
vis[u] = 1;
for (int i = 0; i < g[u].size(); i++) {
int v = g[u][i];
if(!dfn[v]) {//如果没访问过
tarjan(v);
low[u] = min(low[u], low[v]);//能连通到的最小根
}
else if(vis[v])//如果访问过,并且还在栈中
low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u]) {//整个连通分量的最小根时
scc++;
int v;
do {
v = st.top();
st.pop();
vis[v] = 0;
id[v] = scc;
}while (u != v);
}
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; i++) {
g[i].clear();
low[i] = dfn[i] = vis[i] = id[i] = in[i] = out[i] = 0;
}
scc = tot = 0;
while (!st.empty()) st.pop();
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
g[a].push_back(b);
}
if(m == 0) {
printf("%d\n", n);
continue;
}
for (int i = 1; i <= n; i++)
if(!dfn[i]) tarjan(i);
if(scc == 1) {
printf("0\n");
continue;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < g[i].size(); j++) {
int tmp = g[i][j];
if(id[i] != id[tmp]) {//如果该边是横跨了两个强连通分量,则标记
out[id[i]]++;
in[id[tmp]]++;
}
}
}
int tin = 0, tout = 0;
for (int i = 1; i <= scc; i++) {//统计没有出度和入度的强连通分量数
if(in[i] == 0) tin++;
if(out[i] == 0) tout++;
}
printf("%d\n", max(tin, tout));
}
}