【笔记】缩点总结 + P3387 题解
缩点,顾名思义,就是把多个点当成一个点看待。典型的用法就是把强联通分量 SCC 缩为点。
如何判断环?SCC 套一个环的判断即可。当 dfn[n] == low[n] 时说明就有一个强连通分量,具体的请看我以前写的博客:link。
老样子上例题
P3387 【模板】缩点
题目描述
给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行两个正整数 \(n,m\)
第二行 \(n\) 个整数,其中第 \(i\) 个数 \(a_i\) 表示点 \(i\) 的点权。
第三至 \(m+2\) 行,每行两个整数 \(u,v\),表示一条 \(u\rightarrow v\) 的有向边。
输出格式
共一行,最大的点权之和。
样例 #1
样例输入 #1
2 2
1 1
1 2
2 1
样例输出 #1
2
提示
对于 \(100\%\) 的数据,\(1\le n \le 10^4\),\(1\le m \le 10^5\),\(0\le a_i\le 10^3\)。
解析
这道题为什么要缩点呢?因为如果路过一个环就可以经过环上所有的点,而题目规定不能无限计算一个点所以可以把一个环看成一个点。这也是前面所提到的缩点。
走过环所有的点肯定是最划算的因为 \(0\le a_i\le 10^3\)。
找环怎么找前面说了,用 SCC 就行了,SCC 有 3 种常用算法:Tarjan, Garbow, Kosaraju。这里篇幅限制我只介绍 Tarjan。其他两种算法也在这个博客提到了。
缩点本质上还是和 SCC 一个算法,这题的核心其实不是在缩点,这个一会再说, Tarjan代码如下:
void tarjan(int x) {
st[++top] = x;
dfn[x] = low[x] = ++dfncnt;
for (int i = head[x]; i; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) {
tarjan(v);
low[x] = min(low[x], low[v]);
} else if (!vis[v]) {
low[x] = min(low[x], dfn[v]);
}
}
if (dfn[x] == low[x]) {
int v;
while (v = st[top--]) {
circle_belong[v] = x; // 环的归属,就是 v 点归属于哪条环,当 circle_belong[v] == v 时就说明这个点不经过任何环。
vis[v] = 1; // 访问过的记录
if (x == v) { // 环首尾相连了,回到原处就 break
break;
}
pts[x] += pts[v]; // 一个环里的所有点肯定可以缩到一个点
}
}
}
上面是 Tarjan,下面是缩点的核心代码:
input();
rep(i, 1, n) {
if (!dfn[i]) {
tarjan(i);
}
}
rep(i, 1, m) {
int u = circle_belong[E[i].u], v = circle_belong[E[i].v]; // 判断一条边两个点的环归属
if (u != v) { // 不是同一个说明不在环内
E2[++cnt2] = {u, v, head2[u]}; // 建新图
head2[u] = cnt2;
in[v]++; // 这个是点的入边数量,后面题解要用。
}
}
好了,其实缩点的核心内容已经讲完了,接下来讲本题的核心:也就是如何计算最大值。
解析 Ⅱ
以每个环为起点(孤立一个点也算环哈),跑 dp。对于任何一条边 \((u,v)\),可以得到状态转移方程:
其中 \(p_v\) 为所有点的权值。
那么作为 dp,肯定有无后效性问题,这个要用拓扑排序解决。如果说一个点的所有入边已经被遍历过了,就把这个点加入队列里。至于为什么?因为点的遍历顺序你掌控不好直接头脑风暴。
给出完整代码,很长,我调了 2 天:
#include <bits/stdc++.h>
#define rty printf("Yes\n");
#define RTY printf("YES\n");
#define rtn printf("No\n");
#define RTN printf("NO\n");
#define rep(v,b,e) for(int v=b;v<=e;v++)
#define repq(v,b,e) for(int v=b;v<e;v++)
#define rrep(v,e,b) for(int v=b;v>=e;v--)
#define rrepq(v,e,b) for(int v=b;v>e;v--)
#define stg string
#define vct vector
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
void solve() {
}
const int N = 1e4 + 5, M = 1e5 + 5;
int n, m, head[N], cnt, st[N], top, dfncnt;
int pts[N], circle_belong[N], head2[N]; // circle_belong: 环的归属
int dfn[N], low[N];
bool vis[N];
struct edge {
int u, v, next;
}E[M], E2[M];
void add(int u, int v) {
E[++cnt] = {u, v, head[u]};
head[u] = cnt;
}
void input() {
cin >> n >> m;
rep(i, 1, n) {
cin >> pts[i];
}
rep(i, 1, m) {
int u, v; cin >> u >> v;
add(u, v);
}
}
void tarjan(int x) {
st[++top] = x;
dfn[x] = low[x] = ++dfncnt;
for (int i = head[x]; i; i = E[i].next) {
int v = E[i].v;
if (!dfn[v]) {
tarjan(v);
low[x] = min(low[x], low[v]);
} else if (!vis[v]) {
low[x] = min(low[x], dfn[v]);
}
}
if (dfn[x] == low[x]) {
int v;
while (v = st[top--]) {
circle_belong[v] = x;
vis[v] = 1;
if (x == v) {
break;
}
pts[x] += pts[v];
}
}
}
int cnt2, in[N], dist[N];
main() {
// int t; cin >> t; while (t--) solve();
input();
rep(i, 1, n) {
if (!dfn[i]) {
tarjan(i);
}
}
rep(i, 1, m) {
int u = circle_belong[E[i].u], v = circle_belong[E[i].v];
if (u != v) {
E2[++cnt2] = {u, v, head2[u]};
head2[u] = cnt2;
in[v]++;
}
}
queue<int> q;
rep(i, 1, n) {
if (circle_belong[i] == i && !in[i]) {
q.push(i);
dist[i] = pts[i];
}
}
while (q.size()) {
int frt = q.front(); q.pop();
for (int i = head2[frt]; i; i = E2[i].next) {
int v = E2[i].v;
dist[v] = max(dist[v], dist[frt] + pts[v]);
in[v]--;
if (!in[v]) {
q.push(v);
}
}
}
int ans = 0;
rep(i, 1, n) {
ans = max(ans, dist[i]);
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号