【笔记】缩点总结 + P3387 题解
缩点,顾名思义,就是把多个点当成一个点看待。典型的用法就是把强联通分量 SCC 缩为点。
如何判断环?SCC 套一个环的判断即可。当 dfn[n] == low[n]
时说明就有一个强连通分量,具体的请看我以前写的博客:link。
老样子上例题
P3387 【模板】缩点#
题目描述#
给定一个 个点 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式#
第一行两个正整数
第二行 个整数,其中第 个数 表示点 的点权。
第三至 行,每行两个整数 ,表示一条 的有向边。
输出格式#
共一行,最大的点权之和。
样例 #1#
样例输入 #1#
2 2
1 1
1 2
2 1
样例输出 #1#
2
提示#
对于 的数据,,,。
解析#
这道题为什么要缩点呢?因为如果路过一个环就可以经过环上所有的点,而题目规定不能无限计算一个点所以可以把一个环看成一个点。这也是前面所提到的缩点。
走过环所有的点肯定是最划算的因为 。
找环怎么找前面说了,用 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。对于任何一条边 ,可以得到状态转移方程:
其中 为所有点的权值。
那么作为 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;
}
作者:2044-space-elevator
出处:https://www.cnblogs.com/2044-space-elevator/articles/18001879
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!