柿子饼同学 发表于 2024-8-11 21:10:59

[心之碎片] - 图的连通性

专题包括{:10_249:}{:10_266:}{:10_254:}
点双, 边双, 强连通分量, 圆方树

需要注意的是这些割点割边强连通分量的判定方式不要搞混
SCC 序很重要, 有时候不需要建新图, 可以直接通过 SCC 编号来看, SCC 小的是在底下找到的, 大的是在上面找到的
遇到连通性相关要考虑这些算法进行简化, 别忘了线段树优化建图等等辅助
思想就是把图变成树, 做树上问题
有向图的 SCC (强连通分量)

模板
void Tarjan(int u) {
    low = dfn = ++tot;
    stk[++top] = u;
    instk = 1;

    for (auto v : e) {
      if (!dfn) {
            Tarjan(v);

            low = min(low, low);
      } else if (instk)
            low = min(low, dfn);
    }

    if (low == dfn) {
      int v;
      scc++;

      while (1) {
            v = stk;
            instk = 0;
            id = scc;
            if (v == u)
                break;
      }
    }
}

一般就是用来缩点然后把图变成 DAG 的, 可以跑 dp 或者别的
G - 拿钱走人
给个点条边的有向图,每个点有一些钱,当你经过这个点时可以拿走这些钱。
现在有一个起点和多个终点,你需要从起点出发,在某个终点结束,并且带走尽量多的钱,问你最多能带走多少钱?

缩点然后拓扑排序跑 dp
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 5;

vector<int> e, g;

int n, m, p, s;
int dfn, low, tot;
int stk, top;
bitset<N> instk, ised;
int val, cnt, id;
int arr, f;

void Tarjan(int u) {
    dfn = low = ++tot;
    stk[++top] = u;
    instk = 1;

    for (auto v : e) {
      if (!dfn) {
            Tarjan(v);
            low = min(low, low);
      } else if (instk)
            low = min(low, dfn);
    }

    if (low == dfn) {
      int v;
      cnt++;

      while (1) {
            v = stk;
            instk = 0;
            id = cnt;
            val += arr;
            if (u == v)
                break;
      }
    }
}

// g 图上的 DP
int dfs(int u) {
    if (f > 0)
      return f;
    f = -1e9;
    if (ised)
      f = 0;

    for (auto v : g) {
      f = max(f, dfs(v));
    }

    f += val;

    return f;
}

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

    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
      int a, b;
      cin >> a >> b;
      e.emplace_back(b);
    }
    for (int i = 1; i <= n; i++) {
      cin >> arr;
    }
    // 必须注意不能和上面的循环一起写, 调代码的时候注意看当前有没有没求过的数值
    for (int i = 1; i <= n; i++) {
      if (!dfn)
            Tarjan(i);
    }

    cin >> s >> p;
    s = id;
    for (int i = 1; i <= p; i++) {
      int x;
      cin >> x;
      ised] = 1;
    }

    // 建图
    for (int i = 1; i <= n; i++) {
      for (auto j : e) {
            if (id != id) {
                g].emplace_back(id);
            }
      }
    }

    cout << dfs(s);

    return 0;
}

P3119 Grass Cownoisseur G
这里本来应该拓扑排序的, 但是我们只关心能不能到, 所以按照 scc 编号的大小关心来看就好, 最后判断能否拼起来
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

vector<int> e, g;
int n, m;

int dfn, low, tot;
int bel, scc, siz;
int stk, top;
bitset<N> instk;

void Tarjan(int u) {
    dfn = low = ++tot;
    stk[++top] = u;
    instk = 1;

    for (auto v : e) {
      if (!dfn) {
            Tarjan(v);
            low = min(low, low);
      } else if (instk)
            low = min(low, dfn);
    }

    if (dfn == low) {
      int v;
      scc++;

      do {
            v = stk;
            instk = 0;
            bel = scc;
            siz++;
      } while (u != v);
    }
}

// 正向, 反向
int a, b, ans;

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

    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
      int a, b;
      cin >> a >> b;
      e.emplace_back(b);
    }

    for (int i = 1; i <= n; i++) {
      if (!dfn)
            Tarjan(i);
    }

    if(scc == 1){
      cout << siz;
      return 0;
    }

    for (int i = 1; i <= n; i++) {
      for (auto j : e) {
            if (bel != bel) {
                g].emplace_back(bel);
            }
      }
    }

    memset(a, -0x3f, sizeof(a));
    memset(b, -0x3f, sizeof(b));
    a] = siz];
    b] = 0;

    // bel 能到达地方的最大值
    for (int i = bel; i; i--) {
      for (auto j : g) {
            a = max(a, a + siz);
      }
    }
    // 能到达 bel 的最大值
    for (int i = bel + 1; i <= scc; i++) {
      for (auto j : g) {
            b = max(b, b + siz);
      }
    }

    for (int i = 1; i <= scc; i++) {
      for (auto j : g) {
            // 想改变方向的是 i-j 这条边, i 自然是在 bel 上面, j 在 bel 下面
            // 如果不是这样就不用改了, 自己成环了
            if (i >= bel && j <= bel) {
                ans = max(ans, a + b);
            }
      }
    }

    cout << ans;

    return 0;
}

有向图的 vDCC(点双连通分量)eDCC(边双连通分量) 圆方树

BZOJ 3331
建圆方树, 树上差分
vector<int> e, rst;

// 圆方树: 找出所有 vDCC, 每个点断边, 每个点向含有这个点的 vDCC 连边

int dfn, low, tot;
int stk, top, dcc;

void Tarjan(int u) {
    dfn = low = ++tot;
    stk[++top] = u;

    for (auto v : e) {
      if (!dfn) {
            Tarjan(v);
            low = min(low, low);

            if (low >= dfn) {
                int cur = 0;
                dcc++;

                while (1) {
                  cur = stk;
                  rst.emplace_back(dcc);
                  rst.emplace_back(cur);
                  if (cur == v)
                        break;
                }
                rst.emplace_back(dcc);
                rst.emplace_back(u);
            }
      } else
            low = min(low, dfn);
    }
}

一九八四
给一个图, 有操作
1 a b x y 问删掉 (x, y) 这个边, a 和 b 能不能连通
2 a b x 问删掉 x 这个点, a 和 b 能不能连通

先求一下 low 和 dfn, 考虑在搜索树上分类讨论(这玩意一定要分清楚)
// x 是否是 y 的子节点
bool Issub(int x, int y) { return (dfn >= dfn && dfn <= r); }

void Up(int& x, int dist) {
    for (int i = 17; i >= 0; i--) {
      if ((dist >> i) & 1) {
            x = f;
      }
    }
}

bool Checkdege(int a, int b, int x, int y) {
    // 让 x 是 y 的子节点
    if (dfn < dfn)
      swap(x, y);

    // 不是树边
    if (f != y)
      return 1;

    // 都在或者都不在子树
    if (Issub(a, x) == Issub(b, x))
      return 1;

    // 如果有一个点在子树, 判断 (x, y) 是否是割边
    return (low < dfn);
}

bool Checkcutv(int a, int b, int x) {
    // 都不在子树
    if (!Issub(a, x) && !Issub(b, x))
      return 1;

    // 都在子树, 看看 lca 是否是 x
    if (Issub(a, x) && Issub(b, x)) {
      Up(a, dep - dep - 1);
      Up(b, dep - dep - 1);
      return (a == b || (low < dfn && low < dfn));
    }

    // 其中一个在子树, 判断割点
    if (Issub(b, x) && !Issub(a, x))
      swap(a, b);
    Up(a, dep - dep - 1);
    return (low < dfn);
}

FishC 发表于 2024-8-11 21:22:23

好的,让我们来聊聊图的连通性和强连通分量(SCC)的问题,保证你在这个过程中不会迷失在图的海洋中!

### 图的连通性
想象一下,你在一个朋友的聚会上,一个个的朋友就像图中的点(vertex),而朋友之间的关系就像图中的边(edge)。如果所有的朋友都能通过某种关系(直接或间接的)互相联系,那么这个聚会就是“连通”的。反之,如果有一些朋友是孤独的,那就意味着这个聚会并不是完全连通的。

### 强连通分量
对于有向图,我们多了一个概念,那就是“强连通分量”。这就像你有一群朋友,每个人都至少可以通过关系互相到达其他人。比如,如果你可以从A到B再到C,且从C可以回到A,那“A、B、C”可以算作一个强连通分量。这听起来是不是有点复杂?别担心,接下来我们简单讲解如何找到这些强连通分量的方法。

### Tarjan算法
我们可以用**Tarjan算法**来找到强连通分量。想象一下这是你的图灵(图与灵魂的结合)。这个算法的基本思路是深度优先搜索(DFS),如下:

1. **初始化状态**:给每个节点赋初始值。
2. **递归搜索**:当你访问一个子节点时,更新当前节点的`low`值。
3. **形成组件**:如果当前节点的`low`等于其`dfn`值,那么我们找到了一个强连通分量。

使用Tarjan算法就像是在朋友聚会中玩“抓人游戏”,每次抓到的人都要把周围的朋友拉入自己的小队,直到所有能够互相联系的人都被抓进来了。

### 有向图中的最大收益问题
假如你朋友聚会中的每个人都携带了一些现金,你希望从一个起点开始,到达多个终点,同时尽可能多地“收集”这些现金。这里就可以通过之前提到的SCC来“缩点”,然后使用拓扑排序和动态规划来找到最大收益,就像是在聚会上找到最佳的“收钱”路线!

下面是一个简单的框架:
1. **缩点**:将每个强连通分量看作一个点,形成新的DAG(有向无环图)。
2. **DP求解**:从起点开始,利用动态规划来计算每个点可以得到的最大现金。

### 代码示例
虽然最后的代码有点长,但其实它就是上面提到的思想的具体实现。你只需保持思路清晰,确保在任何情况下都不会“迷路”。记得,计算图的时候一定要小心割点和割边,确保你的“朋友聚会”永远是连通的!

### 总结
通过将图转化为树的形式,我们可以更好、更高效地处理各种问题。不管是找朋友还是收钱,紧握住连通性和强连通分量的钥匙,无论何时,你都能在图的世界中游刃有余!

希望你能在图的旅途中一路顺风,收获满满!而且别忘了最重要的一点——即使是图形和数据结构,偶尔也要给自己留点幽默感哦!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

柿子饼同学 发表于 2024-8-11 21:37:16

FishC 发表于 2024-8-11 21:22
好的,让我们来聊聊图的连通性和强连通分量(SCC)的问题,保证你在这个过程中不会迷失在图的海洋中!

# ...

教我一下同余最短路的原理

FishC 发表于 2024-8-11 21:37:27

柿子饼同学 发表于 2024-08-11 21:37
教我一下同余最短路的原理

同余最短路问题可以说是一个“聪明”的问题,因为它要求我们在图中寻找路径时,不仅要考虑到距离,还要考虑到距离的模(即余数)。让我们来通俗易懂、幽默地聊聊它的原理吧!

### 同余最短路背景
首先,想象一下,一个城镇里有很多条道路,每条道路都有一定的长度(比如公里数)。但每次你走的总距离都需要满足某个特定的条件,比如说,最后的总距离对某个数取余之后的结果要等于0。这就好比你的妈妈希望你每次回家带着一定数量的水果,而不是零零散散的。

### 定义
在同余最短路问题中,我们考虑有向图中的每个边(道路),每个边的权重(长度)可能是正数。我们的目标是从起点出发,找到一条路径,使得终点的距离对某个正整数 \( m \) 的模(余数)达到一个特定值(例如 0)。

### 原理
为了找到这样的路径,我们可以用**动态规划**来解决这个问题。这里是基本的思路:

1. **状态定义**:
   - 用 `dp` 表示从起点到节点 \( u \) 的最短路径,并且这条路径的长度对 \( m \) 取余后等于 \( r \)。
   - 其中 \( r \) 可以在 \( 0 \) 到 \( m-1 \) 之间变化。

2. **状态转移**:
   - 当你从节点 \( u \) 转移到节点 \( v \) 时,如果边的权重为 \( w \),那么可以通过如下方式更新状态:
   - 新的余数 \( new\_r = (r + w) \% m \)
   - 更新 \( dp = \min(dp, dp + w) \)
   - 这就像是在为你的水果购物,每次都要加上当前边的权重,然后检查是否符合妈妈的要求(余数)。

3. **初始化**:
   - 初始时,\( dp = 0 \) (表示从起点到起点的路径长度为0,余数自然为0),其他状态设置为无穷大。

4. **最终答案**:
   - 经过所有的节点后,你可以根据需要的余数(例如 \( 0 \))去找终点的 `dp` 状态中最小的路径长度。

### 举个例子
假设一个简单的图,边的权重分别为 1, 2, 3,你希望从起点到达终点的总距离能被 3 整除。

- 除了逐步更新 `dp` 表格外,想象一下,你和朋友们约好了一起买水果,但你必须每次买的数目加起来刚好是 3 的倍数才能获得折扣!每次选择时,你既要计算已经花了多少钱(路径长度),又要考虑现在的水果数量(余数)。

### 总结
同余最短路问题是通过**动态规划**结合模运算的原理来解决的,这种方法适合于处理那些不仅关心路径长度,还要关注路径性质(如余数)的情况。通过划分状态并逐步更新,可以有效找到合适的路径。

希望你在同余最短路的学习过程中,没有迷失在树丛中,而是像个聪明的小狐狸一样,迅速找到最优路径!如果你还有更多问题,随时问我哦!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

希儿的 发表于 2024-8-11 23:28:33


柿子饼同学 发表于 2024-8-14 17:50:34

FishC 发表于 2024-8-11 21:22
好的,让我们来聊聊图的连通性和强连通分量(SCC)的问题,保证你在这个过程中不会迷失在图的海洋中!

# ...

讲 10 个笑话

FishC 发表于 2024-8-14 17:50:46

柿子饼同学 发表于 2024-08-14 17:50
讲 10 个笑话

当然可以!让我们来轻松一下,下面是十个幽默的小笑话,希望能让你开怀一笑:

1. **为什么程序员不喜欢自然?**
   因为他们觉得“没有Bug”的地方太少了!

2. **我告诉我的计算机我希望它慢一点。**
   它现在在犹豫要不要重启。

3. **有两个土豆走在街上,一被车撞了。**
   另一个说:“你还好吗?”
   “我现在是薯条了!”

4. **你知道为什么数学书总是很忧伤吗?**
   因为它有太多的问题!

5. **有一天,狗和猫在讨论各自的优点。**
   狗说:“我能听懂人类的话。”
   猫反驳:“那又怎样?我能让你们一直以为你们是我的主人!”

6. **为什么幽灵总是爱参加聚会?**
   因为他们能够“吸引”所有人的注意!

7. **小时候,我的父母跟我说:‘你是我唯一的孩子’。**
   我毕业后才明白他们那是为了让我好好学习!

8. **有一天,西红柿走过马路,不小心被车撞了。**
   司机急了:“喂,小心点,你这可不是番茄酱!”

9. **为何电脑总是冷?**
   因为它有很多“风扇”!

10. **你听说过那个把电梯当作工作场所的人吗?**
    他总是在“上升”中!

希望这些笑话带给你一丝欢乐!如果你需要更多的幽默或任何其他问题,请随时告诉我!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

柿子饼同学 发表于 2024-8-14 17:52:24

FishC 发表于 2024-8-14 17:50
当然可以!让我们来轻松一下,下面是十个幽默的小笑话,希望能让你开怀一笑:

1. **为什么程序员不 ...

长一点的笑话, 大概 50 字

FishC 发表于 2024-8-14 17:52:31

柿子饼同学 发表于 2024-08-14 17:52
长一点的笑话, 大概 50 字

好的,来一个稍微长一点的笑话,希望你喜欢:

有一位数学老师正在给学生讲解几何,突然一个学生举手问:“老师,为什么圆的周长和直径是固定比值呢?”老师沉默了片刻,幽默地回答:“因为如果不这样,圆就会变成方形,所有的角都得去上班了!”

这下全班都笑了,毕竟,大家都知道,角落里可没有人喜欢加班!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

柿子饼同学 发表于 2024-8-14 17:54:39

FishC 发表于 2024-8-14 17:52
好的,来一个稍微长一点的笑话,希望你喜欢:

有一位数学老师正在给学生讲解几何,突然一个学生举手 ...

我即将打算法竞赛, 但是发现同样的题目, 我没有思路或者思路混乱, 而其他人却能做出来
我有些沮丧, 该怎么办呢

柿子饼同学 发表于 2024-8-14 18:03:05

FishC 发表于 2024-8-14 17:52
好的,来一个稍微长一点的笑话,希望你喜欢:

有一位数学老师正在给学生讲解几何,突然一个学生举手 ...

再来 5 个笑话, 每个大约 60 字

FishC 发表于 2024-8-14 18:23:17

柿子饼同学 发表于 2024-08-14 17:54
我即将打算法竞赛, 但是发现同样的题目, 我没有思路或 ...

首先,别气馁!你并不是一个人在战斗,许多参加算法竞赛的人都有过类似的经历。下面是一些建议,希望可以帮助你理清思路,增强信心:

### 1. **放松心态**
   - 比赛本身就是一个学习和成长的过程。每个人的成长曲线都是不同的,保持平常心很重要。

### 2. **基础知识复习**
   - 确保你对算法和数据结构的基本概念有清晰的理解。重复学习常见的算法(如排序、搜索、动态规划等),熟悉它们的应用场景及实现方法。

### 3. **多做练习**
   - 多刷题,特别是之前的比赛题目。在 LeetCode、Codeforces、AtCoder 等平台上进行练习,尝试不同类型的问题。

### 4. **分析他人的解法**
   - 对于你做不出来的题,可以学习其他人的解法。分析他们的思路和解决方案,看看自己能从中得到哪些启发。

### 5. **逐步拆解问题**
   - 面对复杂的问题,可以尝试将其拆分成多个小问题,逐步解决。这样会让思路变得更加清晰。

### 6. **与他人讨论**
   - 和同伴讨论问题,交流思路。有时候,听到别人的想法会激发你新的灵感,或者让你的思路更清晰。

### 7. **保持积极的自我对话**
   - 给自己积极的暗示,比如“我能做到”或“每次尝试都是一种进步”,这会帮助你保持积极的状态。

### 8. **适当休息**
   - 如果觉得思路混乱,不妨暂时放下,去散步、喝杯水,稍微放松一下大脑,再回过头来思考。

### 9. **设定小目标**
   - 不要一开始就追求高难度题,先从简单的题目做起,逐步提升自己的能力,建立信心。

### 10. **相信过程**
   - 技能的提升需要时间,持续努力和练习终将带来回报。每一次失败都是宝贵的经验。

记住,每个高手都是从新手过来的,关键在于坚持和不断学习。祝你在接下来的算法竞赛中取得好成绩!你一定可以做到的!加油!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

FishC 发表于 2024-8-14 18:23:25

柿子饼同学 发表于 2024-08-14 18:03
再来 5 个笑话, 每个大约 60 字

当然可以,来五个幽默的小故事,保证让你笑得合不拢嘴!

1. 有一天,一只鹦鹉飞到了一个酒吧,服务员惊讶地问:“你会说话吗?”鹦鹉骄傲地说:“当然,我能说五种语言!”服务员又问:“那你怎么飞到这儿的?”鹦鹉说:“我坐飞机!你以为我有那么多时间走路吗?”

2. 一只乌龟在马路上慢慢爬,被一只兔子嘲笑:“你干嘛这么慢,好像在等什么!”乌龟不屑一顾:“我是在享受风景,没必要急匆匆!”兔子翻了个白眼:“风景?你根本看不到前面有车来啊!”乌龟耸肩:“没关系,我的保险杠还在维修呢!”

3. 一位顾客走进理发店,问发型师:“你们这里剪头发需要预约吗?”发型师微笑着回答:“不需要,来就行!我们的座位是免费提供的,剪发费也挺便宜!”顾客好奇:“那为什么大家都要预约?”发型师摇了摇头:“因为大家都怕被剪成一个‘饼’!”

4. 一只小狗对另一只小狗说:“我学会了个新把戏,可以让主人给我零食!”另一只小狗好奇:“怎么做?”小狗自信地说:“我藏起来,然后在晚上叫两声。”第二只小狗疑惑:“这样能有什么用?”小狗眨眨眼:“因为主人最后没法找到我,就会给我一些额外的零食!”

5. 一位教授在课堂上问学生:“大家知道为什么火箭总是朝上飞吗?”一个学生立刻回答:“因为它想逃离地球的引力!”教授微微一笑:“不错,但更重要的是,它不想跟我们一起上课!”全班爆笑,看来知识就是力量,而逃课也是一种智慧!

希望这些笑话能带给你欢乐!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

简柠啦 发表于 2024-8-15 09:39:04

{:10_266:}{:10_266:}

18408238295 发表于 2024-8-16 08:29:04

{:10_266:}向大佬学习学习

咔咔咔米 发表于 2024-8-16 09:40:12

1101378861 发表于 2024-8-16 12:06:29

学习下

鱼不来 发表于 2024-8-17 15:24:42

666666666666

很cool的阳 发表于 2024-8-18 09:56:23

{:7_113:}

18408238295 发表于 2024-8-20 09:59:33

学习学习
页: [1] 2
查看完整版本: [心之碎片] - 图的连通性