有时对于一个有向图我们及其渴望将其变为一个有向无环图,这样我们就要用到强连通分量缩点了。
例题
洛谷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; }
来源:https://www.cnblogs.com/000zwx000/p/12573311.html