鱼C论坛

 找回密码
 立即注册
查看: 1082|回复: 1

[心之碎片][0623] - P2746校园网

[复制链接]
发表于 2024-6-23 22:04:53 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
P2476


知识点
tarjan强连通分量


思路
求出所有强连通分量, 缩点
ans1 = 入度为 0 点的总数
因为显然, 入度为 0 点没法接受到软件
ans2 = 0(scc == 1) 或 max(入度0, 出度0)
想象一条条从起点到终点的有向边, 我们的目标是把这个图变成一整个强连通图
从终点到起点连一条边, 我们就减少了一对终点和起点, 所以连接 max(入度0, 出度0) 条边就能让整个变成 scc
https://www.luogu.com.cn/article/4hmcb1h5


需要进步的地方
ans2 的解法没想出来, 做不来就手玩一些数据, 找找规律然后猜一下
当然, 可以给出一种情况大概证一下 (大概率不会


代码
  1. /*
  2. tarjan 缩点, 每个点属于的 scc 做出来
  3. 入度出度做出来
  4. ans1 = 缩点后入度0的点数
  5. ans2 = max(起点, 终点)
  6. */

  7. #include <bits/stdc++.h>
  8. using namespace std;

  9. const int N = 105;

  10. int n;
  11. vector<int> e[N];

  12. int dfn[N], low[N], tot;
  13. int bel[N], scc_cnt, stk[N], top;
  14. bitset<N> instk;

  15. void Tarjan(int u){
  16.     dfn[u] = low[u] = ++tot;
  17.     stk[++top] = u;
  18.     instk[u] = 1;

  19.     for(auto ed : e[u]){
  20.         if(!dfn[ed]){
  21.             Tarjan(ed);
  22.             low[u] = min(low[u], low[ed]);
  23.             continue;
  24.         }
  25.         if(instk[ed]){
  26.             low[u] = min(low[u], dfn[ed]);
  27.         }
  28.     }

  29.     if(dfn[u] == low[u]){
  30.         int v;
  31.         scc_cnt++;
  32.         do{
  33.             v = stk[top--];
  34.             bel[v] = scc_cnt;
  35.             instk[v] = 0;
  36.         } while(u != v);
  37.     }
  38. }

  39. int din[N], dout[N], in, out;

  40. int main(){
  41.     ios::sync_with_stdio(0);
  42.     cin.tie(0);

  43.     cin >> n;
  44.     for(int i = 1; i <= n; i++){
  45.         int x;
  46.         while(cin >> x){
  47.             if(!x) break;
  48.             e[i].emplace_back(x);
  49.         }
  50.     }

  51.     for(int i = 1; i <= n; i++){
  52.         if(!dfn[i]) Tarjan(i);
  53.     }

  54.     for(int i = 1; i <= n; i++){
  55.         for(auto ed : e[i]){
  56.             if(bel[i] == bel[ed]) continue;
  57.             din[bel[ed]]++;
  58.             dout[bel[i]]++;
  59.         }
  60.     }

  61.     for(int i = 1; i <= scc_cnt; i++){
  62.         if(!din[i]) in++;
  63.         if(!dout[i]) out++;
  64.     }

  65.    
  66.     cout << in << '\n';
  67.     if(scc_cnt == 1) cout << 0;
  68.     else cout << max(in, out);

  69.     return 0;
  70. }
复制代码

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-6-23 22:05:19 | 显示全部楼层
哈哈,好家伙,看到你这段代码和问题描述,我觉得我得拿出传说中的通俗易懂且幽默的解答方式来帮你解惑了。让我们先来拆解一下题目吧。

### 问题解读

我们要解决的问题是找出图中的强连通分量,并缩点,然后计算两个值:
1. `ans1`: 缩点后入度为 0 的顶点数
2. `ans2`: 一些奇妙的东西,与入度和出度有关,我们稍后细说。

### 强连通分量和缩点

首先,什么是强连通分量(SCC,Strongly Connected Component)呢?简单粗暴地说,如果一个有向图中的某个子图,它里面的任意两个点之间都可以互相到达,那么这些点就组成一个强连通分量。

### Tarjan算法

Tarjan算法是一种非常高效的求解强连通分量的算法,具体原理这里不展开细讲,总之,它通过DFS(深度优先搜索)和一些巧妙的数据结构,可以在线性时间内把这些强连通分量给搞出来。

### 缩点

缩点的意思是把每个强连通分量看作一个大点,这样我们的图就变成一个缩点后的图了。在这个缩点图里,每个点实际上是若干个原图中的点的集合,缩点图里的边代表原图中那些跨越了不同强连通分量的边。

### 求解过程

1. 用 Tarjan 算法找到所有的强连通分量,并标记每个节点所属的SCC。
2. 构建缩点后的新图,并计算各个缩点的入度和出度。
3. `ans1` 就是新图中入度为0的点的数量。
4. `ans2` 需要稍微多琢磨一下,我们的目标是尽可能少地添加边,使得整个图变成一个强连通图。通过分析可以发现,答案其实就是 `max(入度为0的缩点数, 出度为0的缩点数)`。
    - 因为如果入度为0的缩点更多,则需要添加至少那么多条边使它们接收到信息。
    - 同理,如果出度为0的缩点更多,则需要添加至少那么多条边使它们能传递信息出去。

### 代码实现

好啦,理论部分完事儿,咱们开始翻译成代码吧。我会稍微修改你的代码,使之更完整和清晰:

  1. #include <bits/stdc++.h>
  2. using namespace std;

  3. const int N = 105;
  4. vector<int> graph[N], e[N];
  5. int dfn[N], low[N], tot;
  6. int scc_cnt, stk[N], top;
  7. int bel[N]; // 每个点属于的强连通分量编号
  8. bitset<N> instk;
  9. int din[N], dout[N];

  10. void Tarjan(int u) {
  11.     dfn[u] = low[u] = ++tot;
  12.     stk[++top] = u;
  13.     instk[u] = 1;
  14.    
  15.     for (auto v : graph[u]) {
  16.         if (!dfn[v]) {
  17.             Tarjan(v);
  18.             low[u] = min(low[u], low[v]);
  19.         } else if (instk[v]) {
  20.             low[u] = min(low[u], dfn[v]);
  21.         }
  22.     }
  23.    
  24.     if (dfn[u] == low[u]) {
  25.         ++scc_cnt;
  26.         int v;
  27.         do {
  28.             v = stk[top--];
  29.             bel[v] = scc_cnt;
  30.             instk[v] = 0;
  31.         } while (u != v);
  32.     }
  33. }

  34. int main() {
  35.     ios::sync_with_stdio(false);
  36.     cin.tie(0);

  37.     int n, x;
  38.     cin >> n;
  39.     for (int i = 1; i <= n; ++i) {
  40.         while (cin >> x && x) {
  41.             graph[i].push_back(x);
  42.         }
  43.     }

  44.     // 使用 Tarjan 算法求解所有强连通分量
  45.     for (int i = 1; i <= n; ++i) {
  46.         if (!dfn[i]) {
  47.             Tarjan(i);
  48.         }
  49.     }

  50.     // 缩点并计算新的入度和出度
  51.     for (int u = 1; u <= n; ++u) {
  52.         for (auto v : graph[u]) {
  53.             if (bel[u] != bel[v]) {
  54.                 e[bel[u]].push_back(bel[v]);
  55.                 din[bel[v]]++;
  56.                 dout[bel[u]]++;
  57.             }
  58.         }
  59.     }

  60.     int ans1 = 0, ans2 = 0;
  61.     for (int i = 1; i <= scc_cnt; ++i) {
  62.         if (din[i] == 0) ans1++;
  63.         if (dout[i] == 0) ans2++;
  64.     }
  65.    
  66.     // 如果只有一个强连通分量,则不需要额外添加边
  67.     if (scc_cnt == 1) ans2 = 0;
  68.    
  69.     cout << ans1 << endl;
  70.     cout << max(ans1, ans2) << endl;

  71.     return 0;
  72. }
复制代码


希望这些解释和代码对你有所帮助!强连通分量和缩点虽然看上去有点复杂,但通过一步步拆解和实现,其实也并没有那么难。加油哦!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-27 17:12

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表