zoukankan      html  css  js  c++  java
  • 算法:强连通分量缩点

    有时对于一个有向图我们及其渴望将其变为一个有向无环图,这样我们就要用到强连通分量缩点了。

    例题

    洛谷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后清空邻接表再重新按原图的边表加边,这样就不容易把原来的图和新的图搞混了。还有记住原图和新图中点的数目是不一样的,务必不要忘记。

    最后再说一下这道题,先缩点,之后有三种方式解决:

    1. 拓扑排序:按照拓扑序进行dp。

    2. 记忆化搜索:直接暴力深搜,再加个记忆化。

    3. 最长路: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
    ", topSort());
    	return 0;
    }
    
  • 相关阅读:
    Yii增删改查
    10个超级有用、必须收藏的PHP代码样例
    yii源码分析I、II
    Yii源码阅读笔记
    Yii源码阅读笔记
    Yii源码阅读笔记
    当浏览器输入url的时候发生了什么
    js模块化值之seaJS
    js判断字符串中的英文和汉字
    display:table-cell的惊天作用,直接惊呆你!
  • 原文地址:https://www.cnblogs.com/000zwx000/p/12573311.html
Copyright © 2011-2022 走看看