• 【题解】P3388 【模板】割点(割顶)


    这题搞了我半天……

    题意

    这题给我们一个图,求这一个图的割点。

    首先我们需要了解割点是什么,简单来说就是删除了这个点之后整个图不是连通图。

    比如下图中的 (②) 就是割点(图片来自OI Wiki

    image

    方法

    OI Wiki上对割点的讲解:link.

    首先用dfs序给每一个节点打上时间戳(图片来自OI Wiki,存在dfn[]数组中:

    image

    我们用像求强连通分量一样的 Tarjan 算法遍历每一个节点,用low[]数组存储从这个节点出发能到达的最远古的节点。

    我们遍历每一个节点,然后更新节点的low[]值。更新方法:

    如果搜索到的下一个节点没有在队列中,搜索完这下一个节点之后,如果这个儿子的low[]值小于自己的low[]值,就将low[]的值更新为它的儿子的low[]值。

    本文中程序的v为当前节点,u为循环到的节点。

          tarjan(u, v);                   //找下面这个点
          low[v] = min(low[v], low[u]);   //这个点low[v]的值就是当前low[]的值与找到的u点的low[]值的最小值
    

    还有可能搜到了还在队列中的节点(即自己的祖先),就取low[v]num[u]的最小值。

    else low[v] = min(low[v], num[u]);   //low[v]就取这个点的low值与循环到的点u的dfn[u]的最小值
    

    接下来就要考虑如何发现割点并把发现的割点的数量记录下来。

    当找到的点不在队列中时,先更新low[]的值,再判断如果low[u] >= num[v],即这个点不能回到祖先节点了,

    并且此时所在的点不是祖先节点,而且此时所在的点没有被标记过,就 flag[v] = true, res++

          if (father != v && low[u] >= num[v] && !flag[v]) flag[v] = true, res++; //如果满足割点的条件就标记是割点,并且让答案的个数增加。
    

    另外,如果此时所在的点是祖先节点,并且它有两个及以上的儿子 (只有不在队列中的点才算儿子),而且没被标记过,就 flag[v] = true, res++

      if (father == v && child >= 2 && !flag[v])    //如果自己有两个儿子并且没有被访问过
                                                    //只有不在队列中的点才算儿子。
        flag[v] = true, res++;
    

    上面的儿子可以在遍历每一个节点的时候累加。

    代码

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    inline int read() {
      int X = 0; bool flag = 1; char ch = getchar();
      while (ch < '0' || ch > '9') {if (ch == '-') flag = 0; ch = getchar();}
      while (ch >= '0' && ch <= '9') {X = (X << 1) + (X << 3) + ch - '0'; ch = getchar();}
      if (flag) return X;
      return ~ (X - 1);
    }
    
    inline void write(int X) {
      if (X < 0) {putchar('-'); X = ~ (X - 1);}
      int s[50], top = 0;
      while (X) {s[++top] = X % 10; X /= 10;}
      if (!top) s[++top] = 0;
      while (top) putchar(s[top--] + '0');
      putchar(' ');
      return;
    }
    
    int head[20004], low[20004], num[20004];
    int index, res, cnt;
    bool flag[20004], vis[20004];
    
    vector<int> edge[100001];
    
    void tarjan(int v, int father) {      //Tarjan算法
      num[v] = low[v] = ++index;          //标记dfn[]访问顺序,还有low[]的初始值
      vis[v] = true;                      //标记这个点被访问过
      int child = 0;
      for (auto u : edge[v]) {
        if (!vis[u]) {                    //如果u没有被访问过
          child++;
          tarjan(u, v);                   //找下面这个点
          low[v] = min(low[v], low[u]);   //这个点low[v]的值就是当前low[]的值与找到的u点的low[]值的最小值
          if (father != v && low[u] >= num[v] && !flag[v]) flag[v] = true, res++; //如果满足割点的条件就标记是割点,并且让答案的个数增加。
        } else low[v] = min(low[v], num[u]);        //low[v]就取这个点的low值与循环到的点u的dfn[u]的最小值
      }
      if (father == v && child >= 2 && !flag[v])    //如果自己有两个儿子并且没有被访问过
                                                    //只有不在队列中的点才算儿子。
        flag[v] = true, res++;
    }
    
    int main() {
      int n, m;
      n = read(); m = read();
      for (int i = 1; i <= m; i++) {
        int x, y;
        x = read(); y = read();
        edge[x].push_back(y);
        edge[y].push_back(x);
      }
      for (int i = 1; i <= n; i++) 
        if (!vis[i]) index = 0, tarjan(i, i);
      write(res);
      putchar('
    ');
      for (int i = 1; i <= n; i++) if (flag[i]) write(i);
      putchar('
    ');
      return 0;
    }
    
  • 相关阅读:
    MyBatis:分页的实现
    Mybatis之配置文件
    Java之创建线程的方式四:使用线程池
    Java之创建线程的方式三:实现Callable接口
    Java之线程通信的应用:经典例题:生产者/消费者问题
    Java之线程通信的方法
    Java之解决线程安全问题的方式三:Lock锁
    Java之同步方法处理实现Runnable接口的线程安全问题
    Java之同步方法处理继承Thread类的线程安全问题
    01 while 循环输入1 2 3 4 5 6 8 9 10
  • 原文地址:https://www.cnblogs.com/g-mph/p/14655556.html
Copyright © 2020-2023  润新知