【笔记】缩点总结 + P3387 题解

缩点,顾名思义,就是把多个点当成一个点看待。典型的用法就是把强联通分量 SCC 缩为点。

如何判断环?SCC 套一个环的判断即可。当 dfn[n] == low[n] 时说明就有一个强连通分量,具体的请看我以前写的博客:link

老样子上例题

P3387 【模板】缩点#

题目描述#

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

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

输入格式#

第一行两个正整数 n,m

第二行 n 个整数,其中第 i 个数 ai 表示点 i 的点权。

第三至 m+2 行,每行两个整数 u,v,表示一条 uv 的有向边。

输出格式#

共一行,最大的点权之和。

样例 #1#

样例输入 #1#

2 2
1 1
1 2
2 1

样例输出 #1#

2

提示#

对于 100% 的数据,1n1041m1050ai103

解析#

这道题为什么要缩点呢?因为如果路过一个环就可以经过环上所有的点,而题目规定不能无限计算一个点所以可以把一个环看成一个点。这也是前面所提到的缩点。

走过环所有的点肯定是最划算的因为 0ai103

找环怎么找前面说了,用 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),可以得到状态转移方程:

dv=max{du+pv}

其中 pv 为所有点的权值。

那么作为 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 国际」许可协议进行许可。

posted @   CoutingStars  阅读(4)  评论(0编辑  收藏  举报
编辑推荐:
· 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框架的用法!
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示