强连通:有向图中,如果任意2点都相互可达,则该图是强连通图。
强连通分量:有向图中,其强连通图子图,称为强连通分量。(缩点后每个点都原图中最大的强连通分量)
一个有向图是强连通的,等价于G中有一个回路,它至少包含每个节点一次。(只是一笔画经过所有点回到原点,点可以通过多次,不一定是一个大环,也可能是几个小环的拼接。但环上的所有点一定构成强连通分量)。
一些问题只要变成有向无环图就容易解决,但其中有环就比较难办,而环等价于强连通分量,把每个强连通分量缩成一个点,就是dag了。
常用算法是tarjan算法,复杂度是O(n+m),线性的。(注意有很多个tarjan算法..这个是求强连通的,还有离线求lca的,求双联通的..)
模板比较简单而且不怎么灵活,主要是一些推论。
比如一些简单推论:
从任一点出发都可以到达的点有几个?
缩点后如果出度为0的点集唯一,符合条件的点就是该点集中的点,否则不存在符合条件的点。(poj 2186)
最小点基:选择最少的点,使得从这些点出发可以到达所有点。
最小权点基:选择权和尽量小的点集,使得从这些点出发可以到达所有点。(hdu5934)
解法:入度为0的强连通分量个数即为最小点基,从每个入度为0的强连通分量中取出权值最小的点,构成的集合即最小权点基。
最少加多少边能使图变为强连通图?(poj1236)
Ans = max(缩点后入度为0的点集,缩点后出度为0的点集)(特判如果缩点后只有一个点则原图已经强连通,ans = 0)
以poj1236为例(求最小点基点数,及至少加几个点变成强连通图),比较模板的写法(主函数中只需要调用,缩点后的信息都有直接处理好,加边后跑一遍当做黑盒用就好..)
#include
#define ll long long
#define mm 10005
using namespace std;
stack<int> sta;
bool vis[mm], in[mm], out[mm];
//vis点是否在栈中 缩点后的点是否有出度入度
int n, m, tim, num, cnt;
//tim点的标记 num强连通分量个数(缩点后的点数) cnt链表计数 都是从1开始
int h[mm], dfn[mm], low[mm], siz[mm], bel[mm];
//bel:u是属于哪个集合中的 siz[i]第i个强连通的点数
int xx[mm], yy[mm];//备份边
struct
{
int to, ne;
} ed[mm * 5];
void init()
{
memset(vis, 0, sizeof(vis)), memset(in, 0, sizeof(in));
memset(h, 0, sizeof(h)), memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low)), memset(bel, 0, sizeof(bel));
memset(siz, 0, sizeof(siz)), memset(out, 0, sizeof(out));
cnt = tim = num = 0;
while (!sta.empty())
sta.pop();
}
void add(int u, int v)
{
ed[++cnt].to = v, ed[cnt].ne = h[u], h[u] = cnt;
xx[cnt] = u, yy[cnt] = v;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
vis[u] = 1;
sta.push(u);
for (int i = h[u]; i; i = ed[i].ne)
{
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (vis[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])//发现新的强连通分量
{
int v;
num++;
do
{
v = sta.top(), sta.pop();
vis[v] = 0, bel[v] = num;
siz[num]++;//num从1开始
} while (u != v);
}
}
int main()
{
int i, j, te;
while (~scanf("%d", &n))
{
init();
for (i = 1; i <= n; i++)
while (scanf("%d", &te) && te)
add(i, te);
for (i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
//缩点后共num个点 i缩点后为bel[i] 缩点后第x个点中有原siz[x]个点
for (i = 1; i <= cnt; i++)
if (bel[xx[i]] != bel[yy[i]])
out[bel[xx[i]]] = 1, in[bel[yy[i]]] = 1;
//点集bel[x[i]]有出度 bel[y[i]]有入度
if (num == 1)
{
puts("1\n0");
continue;
}
int ans1 = 0, ans2 = 0;
for (i = 1; i <= num; i++)
{
if (in[i] == 0)
ans1++;
if (out[i] == 0)
ans2++;
}
printf("%d\n%d\n", ans1, max(ans1, ans2));
}
return 0;
}
计蒜客 百度科学家(中等)直接建图缩点找出度为0的点集,算出每个点集的权和即可(然而ac不了数据极少的简单版...有点谜 困难版据说是可持久化线段树优化建图..)