算法:强连通分量缩点
有时对于一个有向图我们及其渴望将其变为一个有向无环图,这样我们就要用到强连通分量缩点了。
例题
洛谷3387 缩点
题目背景
缩点+DP。
题目描述
给定一个 n个点 m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数n,m。
第二行n个整数,依次代表点权。
第三至(m + 2)行,每行两个整数u,v,表示一条u -> v的有向边。
输出格式
共一行,最大的点权之和。
输入输出样例
输入
2 2
1 1
1 2
2 1
输出
2
说明/提示
对于 100%的数据,1 <= n <= 10^4,1 <= m <= 10^5,0 <= 点权 <= 10^3。
强连通分量缩点
对于一道图论题,有时我们会发现如果说这是一个有向无环图会很好解决,但题目中却并没有说无环,这时我们希望将这个有向图变成一个有向无环图,这就要用到强连通分量缩点了。
在一个强连通分量中,我们知道任意两个点可以互相到达,那么其实我们就可以利用这个特点去对其进行缩点,将原图变成一个有向无环图。
在做tarjan算法时,我们已经对每个点进行了染色,所以这样缩点就很简单了,如果一条边起点u和终点v的颜色不一样,就以u的颜色color[u]为起点、v的颜色color[v]为终点建一条边。
竞赛中,我们为了不把原来的图和新的图搞混,往往不会建两个图,而是先以边表的形式储存一下原图,之后等跑完tarjan后清空邻接表再重新按原图的边表加边,这样就不容易把原来的图和新的图搞混了。还有记住原图和新图中点的数目是不一样的,务必不要忘记。
最后再说一下这道题,先缩点,之后有三种方式解决:
-
拓扑排序:按照拓扑序进行dp。
-
记忆化搜索:直接暴力深搜,再加个记忆化。
-
最长路:spfa跑一个最长路。
这里我就用思路最简单的拓扑排序来写了。
最后算一下算法时间复杂度:我们发现其中就是一个tarjan和一个拓扑排序,综合一下也就O(n + m)级别了。
代码
# include <cstdio>
# include <algorithm>
# include <cmath>
# include <cstring>
# include <vector>
# include <queue>
# include <stack>
using namespace std;
const int N_MAX = 10000, M_MAX = 100000;
int n, m;
int u[M_MAX + 10], v[M_MAX + 10];
int a[N_MAX + 10]; // a[i]表示小点i的权值
vector <int> g[N_MAX + 10];
// 一大堆tarjan算法所用到的变量
int now, dfn[N_MAX + 10], low[N_MAX + 10];
bool ins[N_MAX + 10];
stack <int> s;
int color[N_MAX + 10], cnt[N_MAX + 10];
int in[N_MAX + 10];
queue <int> q;
int w[N_MAX + 10], dp[N_MAX + 10]; // w[i]表示大点i的权值
void addEdge(int x, int y)
{
g[x].push_back(y);
}
void tarjan(int x)
{
dfn[x] = low[x] = ++now;
ins[x] = true;
s.push(x);
for (int i = 0; i < (int) g[x].size(); i++) {
int y = g[x][i];
if (dfn[y] == 0) tarjan(y), low[x] = min(low[x], low[y]);
else if (ins[y]) low[x] = min(low[x], dfn[y]);
}
if (low[x] != dfn[x]) return;
color[x] = ++color[0];
cnt[color[0]]++;
while (s.top() != x) {
int y = s.top();
color[y] = color[0];
ins[y] = false;
s.pop();
cnt[color[0]]++;
}
ins[x] = false;
s.pop();
}
void resetPoint()
{
for (int i = 1; i <= n; i++)
if (dfn[i] == 0) tarjan(i);
memset(g, 0, sizeof(g)); // 重置所有的边
for (int i = 1; i <= m; i++) {
int x = color[u[i]], y = color[v[i]];
if (x != y) addEdge(x, y);
}
}
int topSort()
{
for (int x = 1; x <= color[0]; x++)
for (int i = 0; i < (int) g[x].size(); i++)
in[g[x][i]]++;
for (int i = 1; i <= color[0]; i++)
if (in[i] == 0) dp[i] = w[i], q.push(i);
int ans = 0;
while (!q.empty()) {
int x = q.front();
q.pop();
ans = max(ans, dp[x]);
for (int i = 0; i < (int) g[x].size(); i++) {
int y = g[x][i];
dp[y] = max(dp[y], dp[x] + w[y]);
if (--in[y] == 0) q.push(y);
}
}
return ans;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u[i], &v[i]);
addEdge(u[i], v[i]);
}
resetPoint();
for (int i = 1; i <= n; i++)
w[color[i]] += a[i];
printf("%d\n", topSort());
return 0;
}