0%

P3387 【模板】缩点

P3387.【模板】缩点

题目描述:

给出一个 $n$ 个点, $m$ 条边的有向图,每个点有一个权值,求一条路径,使得路径经过的点权值之和最大,只需要输出这个权值和。

允许多次经过一条边或者一个点,但是重复经过的点,权值只计算一次。

数据范围:

$1\le n \le 10^4, 1\le m \le 10^5;0\le a_i\le 10^3$

题解:

因为可以多次经过,但是权值只会计算一次,所以可以进行缩点,将图中的环缩成一个点,图就变成了有向无环图DAG。缩点时需要记录新的强连通分量的权值,新点的权值就是整个强连通分量的权值之和。

最后使用动态规划求解最大权值。一个 DAG 路径的最大权值很显然 $dp[v] = \max(dp[v], dp[u] + value[v])$ 。但是在图中如何dp呢?这就需要用到拓扑排序了,拓扑排序可以消除后效性,按照拓扑排序的顺序对图进行dp,最后得到解。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
using namespace std;
using namespace FAST_IO;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INF_LL = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-5;
const int maxn = 1e4 + 10;
const int maxm = 1e5 + 10;
int t, n, m, k;
int value1[maxn];
vector<int> g1[maxn], g2[maxn];

int dfn[maxn], low[maxn], dfn_clock = 0;
int st[maxn], top = -1;
bool vis[maxn];
int scc_id[maxn], value2[maxn], scc_cnt = 0;

int indeg[maxn], dp[maxn];
void tarjan(int u)
{
dfn[u] = low[u] = ++dfn_clock;
vis[u] = true;
st[++top] = u;
for (int i = 0; i < g1[u].size(); ++i)
{
int v = g1[u][i];
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[v], low[u]);
}
else
{
if (vis[v])
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u])
{
++scc_cnt;
while (true)
{
scc_id[st[top]] = scc_cnt;
vis[st[top]] = false;
value2[scc_cnt] += value1[st[top]];
if (st[top--] == u)
break;
}
}
}

void topo()
{
queue<int> q;
for (int i = 1; i <= scc_cnt; ++i)
{
if (!indeg[i])
{
q.push(i);
dp[i] = value2[i];
}
}
while (q.size())
{
auto u = q.front();
q.pop();
for (int i = 0; i < g2[u].size(); ++i)
{
int v = g2[u][i];
dp[v] = max(dp[v], dp[u] + value2[v]);
--indeg[v];
if (!indeg[v])
q.push(v);
}
}
}
int main()
{
// #define COMP_DATA
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
read(n, m);
for (int i = 1; i <= n; ++i)
read(value1[i]);
for (int i = 1, u, v; i <= m; ++i)
{
read(u, v);
g1[u].emplace_back(v);
}
for (int i = 1; i <= n; ++i)
{
if (!dfn[i])
tarjan(i);
}
// 建立新图
for (int u = 1; u <= n; ++u)
{
int uu = scc_id[u];
for (int j = 0; j < g1[u].size(); ++j)
{
int v = g1[u][j];
int vv = scc_id[v];
if (uu == vv)
continue;
g2[uu].emplace_back(vv);
indeg[vv]++;
}
}
// topo 排序
topo();
int ans = 0;
for (int i = 1; i <= scc_cnt; ++i)
ans = max(ans, dp[i]);
cout << ans << endl;
return 0;
}