缩点算法学习笔记

缩点

说说缩点,缩点可以算是强连通分量的一个小小的进阶。

本博客也可以理解为 P3387 【模板】缩点 - 传送门 的题解。

正片开始——


一 题目分析

求有向图上的一条路径,使该路径上点的权值和最大,输出和的最大值(可以重复经过点和边)。

啊当然了,你可以使用 spfa 或者 dijkstra 以 ac 这道题,这里说说缩点的方法。

首先,对于任何一个强连通分量里面,我们可以拿到所有的权值,所以我们将所有
的强连通分量缩成一个个“超级点”,最后找到一条类似链的路径使这条“链”的权值最大。

缩点,是我们在解决强连通分量的题目时可以大大简化该题的工具。

二 缩点操作

这题我们来真真正正地“物理缩点”。

  1. 在最初建边时把每一条边的 (u, v) 都存下来;

  2. 清空关于前向星的数组结构体变量;

  3. 遍历每一条边,若该边的初始点和终点不在同一强连通分量中,就建该边;

以上就是缩点操作了,很简单 ,代码实现如下。

	cnt = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	for (int i = 1; i <= m; i++)
	{
		if (co[x[i]] != co[y[i]])
		{
			add (co[x[i]], co[y[i]]);
		}
	}

三 求路径

这里有 2 种做法:

  • 记忆化搜索;

  • 拓扑排序 + DP 。

很明显第一种比第二种简单,所以我在这里说第一种,至于第二种,请到 强连通分量难题整理 + 题解 看例题 2 ,该题就是用第二种方法。

算法过程:

  1. 建数组 size 并在 Tarjan 的时候求出每一个强连通分量的 size ,再建数组 f ,其作用跟 spfa 中的 dis 数组相同;

  2. for 循环从 1 到 col 选择节点;

  3. 若该节点没有被搜索到过,则 search (i) ;

  4. 首先 f[i] = size[i] ,找 i 连出去所有的边,然后从若干终点中选择 f[v] 最大的点并加入 f[i] 即可。若 f[v] 没有被搜索过,直接 search (i) 。

我们有先后顺序以及一个类似回溯的操作,所以可以保证它的正确性。

具体代码实现如下(主函数那部分较为简单,在这不做展示)。

void search (int u)
{
	if (f[u]) return;
	f[u] = sum[u];
	int maxsum = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!f[v]) search (v);
		maxsum = max (maxsum, f[v]);
	}
	f[u] += maxsum;
}

四 板子代码

#include<bits/stdc++.h>
using namespace std;

const int maxn = 150005;
int n, m;
int cnt, hd[maxn];
struct node{
	int to, nxt;
}e[maxn * 2];
int dfn[maxn], low[maxn];
int top, st[maxn], de[maxn], si[maxn];
int col, co[maxn];
int x[maxn], y[maxn];
int tmp, ans;
int f[maxn], sum[maxn];
int w[maxn];

void add (int u, int v)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u];
	hd[u] = cnt;
}

void tarjan (int u)
{
	dfn[u] = low[u] = ++tmp;
	st[++top] = u;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (!co[v]) low[u] = min (low[u], dfn[v]);
	}
	if (dfn[u] == low[u])
	{
		co[u] = ++col;
		sum[col] += w[u];
		while (st[top] != u)
		{
			co[st[top]] = col;
			sum[col] += w[st[top]];
			--top;
		}
		--top;
	}
}

void search (int u)
{
	if (f[u]) return;
	f[u] = sum[u];
	int maxsum = 0;
	for (int i = hd[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!f[v]) search (v);
		maxsum = max (maxsum, f[v]);
	}
	f[u] += maxsum;
}

int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf ("%d", &w[i]);
	for (int i = 1; i <= m; i++)
	{
		scanf ("%d %d", &x[i], &y[i]);
		add (x[i], y[i]);
	}
	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i]) tarjan (i);
	}
	cnt = 0;
	memset (hd, 0, sizeof hd);
	memset (e, 0, sizeof e);
	for (int i = 1; i <= m; i++)
	{
		if (co[x[i]] != co[y[i]])
		{
			add (co[x[i]], co[y[i]]);
		}
	}
	for (int i = 1; i <= col; i++)
	{
		if (!f[i]) 
		{
			search (i);
			ans = max (ans, f[i]);
		}
	}
	printf ("%d\n", ans);
	return 0;
}

如果对缩点理解透彻且熟知,它将成为我们做强连通分量题目很好的工具。

啊当然了,你不会缩点强连通分量的难题你也应该做不出来的。

—— E n d End End——

posted @ 2022-03-25 07:26  pldzy  阅读(57)  评论(0)    收藏  举报