强连通分量的模板题,设 a 为出度为0的点个数,设 b 为入度为0 的点个数,要求的是a和max(a,b)。
把处于一个强连通分量的点染色,再求出度和入度即可。
这里的模板是两个dfs,一个原图,一个逆图,两遍dfs即可求得结果,比较好理解。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
#define clr(a,b) memset(a,b,sizeof(a))
#define pb(x) push_back(x)
/*#define debug printf("test\n");
typedef pair<int,int> pii;*/
vector<int> g[105]; //原图
vector<int> rg[105]; //反向图
vector<int> ng[105]; //新图
stack<int> s;
int n,cnt,dfn[105],belong[105],id,vis[105],flag,ind[105],od[105]; //belong 缩点
void dfs(int x) {
if(vis[x] == flag) return ;
vis[x] = flag;
for(int i = 0;i < g[x].size();i++) dfs(g[x][i]);
s.push(x);
}
void dfs2(int x) {
if(vis[x] == flag) return ;
vis[x] = flag; belong[x] = id;
for(int i = 0;i < rg[x].size();i++) dfs2(rg[x][i]);
}
int main() {
int n;
while(scanf("%d",&n) != EOF) {
for(int i = 0;i <= n;i++) {
g[i].clear();rg[i].clear();
}
for(int i = 1;i <= n;i++) {
int v;
while(scanf("%d",&v) != EOF && v) {
g[i].pb(v);rg[v].pb(i);
}
}
flag = 1;
clr(vis,0);
for(int i = 1;i <= n;i++) {
if(vis[i] != flag) {
dfs(i);
}
}
flag = 2;id = 0;
for(int i = 1;i <= n;i++) {
int tmp = s.top();
if(vis[tmp] != flag) {
++id;
dfs2(tmp);
}
s.pop();
}
clr(ind,0);clr(od,0);
for(int i = 1;i <= n;i++) {
for(int j = 0;j < g[i].size();j++) {
int v = g[i][j];
if(belong[i] == belong[v]) continue;
ind[belong[v]]++;
od[belong[i]]++;
}
}
if(id == 1) {
printf("1\n0\n");
continue;
}
int incnt = 0,outcnt = 0;
for(int i = 1;i <= id;i++) {
if(!ind[i]) incnt++;
if(!od[i]) outcnt++;
}
printf("%d\n%d\n",incnt,max(outcnt,incnt));
}
}
下面再贴一个大白书上的强连通分量的模板,这个是tarjan三大算法之一,复杂度为O(m+n),代码比较简洁。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <stack>
#include <bitset>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <algorithm>
using namespace std;
#define clr(a,b) memset(a,b,sizeof(a))
#define pb(a) push_back(a)
#define fir first
#define se second
#define LL long long
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
typedef pair<LL,LL> pll;
const int maxn = 1005;
const LL inf = 0x3f3f3f3f3f3f3f3f;
LL mod = 1e9+7;
double PI = acos(-1);
vector<int> g[maxn];
int dfn[maxn],low[maxn],belong[maxn],dfs_clock,scc_cnt;
stack<int> s;
//这里的dfn为dfs的访问次序,low为能追溯到的最早的次序,sccno为每个点在第几个强连通分量中
//dfs_clock是dfs的顺序,scc_cnt是强连通分量的数量
//栈用来存每次dfs的连通分量
void dfs(int u) { //每次dfs结束都找到一个强连通分量
dfn[u] = low[u] = ++dfs_clock;
s.push(u);
for(int i = 0;i < g[u].size();i++) {
int v = g[u][i];
if(!dfn[v]) {
dfs(v);
low[u] = min(low[u],low[v]);
}
else if(!belong[v]) {
low[u] = min(low[u],dfn[v]);
}
}
if(low[u] == dfn[u]) {
scc_cnt++;
for(;;) {
int x = s.top();s.pop();
belong[x] = scc_cnt;
if(x == u) break;
}
}
}
void find_scc(int n) { //这里的图是从1~~n.
dfs_clock = scc_cnt = 0;
memset(belong,0,sizeof(belong)); //belong缩点
memset(dfn,0,sizeof(dfn));
for(int i = 1;i <= n;i++) { //dfn为0则表示没有遍历过该点
if(!dfn[i]) dfs(i);
}
}
int ind[1005],od[1005];
int main() {
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
int x;
while(scanf("%d",&x) != EOF && x != 0) {
g[i].pb(x);
}
}
find_scc(n);
/*for(int i = 1;i <= n;i++) printf("%d ",belong[i]);
cout<<endl;*/
memset(ind,0,sizeof(ind));
memset(od,0,sizeof(od));
for(int i = 1;i <= n;i++) {
for(int j = 0;j < g[i].size();j++) { //缩点后求出度和入度
int v = g[i][j];
if(belong[i] == belong[v]) continue;
ind[belong[v]]++;
od[belong[i]]++;
}
}
if(scc_cnt == 1) {
printf("1\n0\n");
return 0;
}
int incnt = 0,outcnt = 0;
for(int i = 1;i <= scc_cnt;i++) {
if(!ind[i]) incnt++;
if(!od[i]) outcnt++;
}
printf("%d\n%d\n",incnt,max(incnt,outcnt));
}