缩点
说说缩点,缩点可以算是强连通分量的一个小小的进阶。
本博客也可以理解为 P3387 【模板】缩点 - 传送门 的题解。
正片开始——
一 题目分析
求有向图上的一条路径,使该路径上点的权值和最大,输出和的最大值(可以重复经过点和边)。
啊当然了,你可以使用 spfa 或者 dijkstra 以 ac 这道题,这里说说缩点的方法。
首先,对于任何一个强连通分量里面,我们可以拿到所有的权值,所以我们将所有
的强连通分量缩成一个个“超级点”,最后找到一条类似链的路径使这条“链”的权值最大。
缩点,是我们在解决强连通分量的题目时可以大大简化该题的工具。
二 缩点操作
这题我们来真真正正地“物理缩点”。
-
在最初建边时把每一条边的 (u, v) 都存下来;
-
清空关于前向星的数组结构体变量;
-
遍历每一条边,若该边的初始点和终点不在同一强连通分量中,就建该边;
以上就是缩点操作了,很简单 ,代码实现如下。
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 ,该题就是用第二种方法。
算法过程:
-
建数组 size 并在 Tarjan 的时候求出每一个强连通分量的 size ,再建数组 f ,其作用跟 spfa 中的 dis 数组相同;
-
for 循环从 1 到 col 选择节点;
-
若该节点没有被搜索到过,则 search (i) ;
-
首先 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——