割点模板P3388

复制代码



1
package pro.proclass; 2 3 import java.io.BufferedReader; 4 import java.io.InputStreamReader; 5 import java.util.Arrays; 6 import java.util.StringTokenizer; 7 8 // 割点模板题目 9 public class P3388 { 10 // 链式向前星边的实例 11 static class Edge { 12 int to; 13 int next; 14 15 public Edge(int to, int next) { 16 this.to = to; 17 this.next = next; 18 } 19 } 20 21 // dfn 存储无向图的访问顺序的编号 22 // low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边) 23 // cnt[u]用来记录u点是割点 24 // head 链式向前星的数据结构数组 25 private static int[] dfn, low, cnt, head; 26 27 // root 全局根节点,用来判断根节点是不是割点 28 // num 访问顺序编号全局变量 29 // n 图中点的个数 30 // m 图中边的个数 31 // numb 链式向前星边的编号 32 private static int root, num, n, m, numb; 33 private static Edge[] edges; 34 private static int maxn = 100010, maxm = 500010; 35 36 // 添加边 37 private static void add(int u, int v) { 38 edges[numb] = new Edge(v, head[u]); 39 head[u] = numb++; 40 } 41 42 public static void main(String[] args) throws Exception { 43 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 44 StringTokenizer st = new StringTokenizer(br.readLine()); 45 n = Integer.parseInt(st.nextToken()); 46 m = Integer.parseInt(st.nextToken()); 47 dfn = new int[maxn]; // 访问顺序存储数组 48 low = new int[maxn]; // 访问父节点 49 cnt = new int[maxn]; 50 edges = new Edge[2*maxm]; 51 head = new int[maxn]; 52 Arrays.fill(head, -1); 53 numb = 0; 54 55 for (int i = 0; i < m; i++) { 56 st = new StringTokenizer(br.readLine()); 57 int x = Integer.parseInt(st.nextToken()); 58 int y = Integer.parseInt(st.nextToken()); 59 add(x, y); 60 add(y, x); 61 } 62 63 num = 0; 64 for (int i = 1; i <= n; i++) { 65 if(dfn[i] == 0) { // 如果当前点没有被访问就开始寻找割点 66 root = i; // 起始点为根节点 67 targan(i); 68 } 69 } 70 71 int ans = 0; 72 for (int i = 1; i <= n; i++) { 73 if(cnt[i] == 1) ans++; 74 } 75 76 StringBuilder sb = new StringBuilder(); 77 for (int i = 1; i <= n; i++) { 78 if(cnt[i] == 1) sb.append(i+" "); 79 } 80 System.out.println(ans); 81 System.out.println(sb.toString()); 82 } 83 84 private static void targan(int x) { 85 dfn[x] = low[x] = ++num; // 访问顺序初始化时 dfn和low一样 86 int count = 0; // 计算跟节点的分支 87 88 // 链式向前星遍历边的编号 89 for (int n = head[x]; n != -1 ; n = edges[n].next) { 90 int i = edges[n].to; 91 92 if(dfn[i] == 0) { // 没有被访问 93 count++; // 根节点分支加一 94 targan(i); // 深度搜索 95 // 找到最底层回溯进行更新当前x点的low值,当前点low和回溯子节点low取小 96 low[x] = Math.min(low[x], low[i]); 97 // 如果x是根节点,并且 根节点的分支大于1 说明根节点是割点,进行标记 98 // 如果x不是根节点,但是x的dfn小于子节点的 low值, 说明走向子节点i必须经过父节点x,所以父节点是割点 99 if((x == root && count > 1) || (x != root && dfn[x] <= low[i])) { 100 cnt[x] = 1; 101 } 102 } 103 // 如果找到最底部已经被访问过,更新当前点的low通过回溯边的 dfn比较 104 // 如果是最后一个节点,因为是无向边会更新父节点的dfn为自己low, 所以判断<= 如果求割边,这里需要排除父节点 并且判断改为《 105 low[x] = Math.min(low[x], dfn[i]); 106 } 107 } 108 }
复制代码

 

题目背景

割点

题目描述

给出一个 nn 个点,mm 条边的无向图,求图的割点。

输入格式

第一行输入两个正整数 n,mn,m。

下面 mm 行每行输入两个正整数 x,yx,y 表示 xx 到 yy 有一条边。

输出格式

第一行输出割点个数。

第二行按照节点编号从小到大输出节点,用空格隔开。

输入输出样例

输入 #1
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
输出 #1
1 
5

说明/提示

对于全部数据,1\leq n \le 2\times 10^41n2×104,1\leq m \le 10^51m105。

点的编号均大于 00 小于等于 nn。

tarjan图不一定联通。

 

 

 

 

提交的时候需要new 线程

参考代码如下:

复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

// 割点模板题目
public class Main implements Runnable {
    @Override
    public void run() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;
        try {
            st = new StringTokenizer(br.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        }
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        dfn = new int[maxn]; // 访问顺序存储数组
        low = new int[maxn]; // 访问父节点
        cnt = new int[maxn];
        edges = new Edge[2*maxm];
        head = new int[maxn];
        Arrays.fill(head, -1);
        numb = 0;

        for (int i = 0; i < m; i++) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (IOException e) {
                e.printStackTrace();
            }
            int x = Integer.parseInt(st.nextToken());
            int y = Integer.parseInt(st.nextToken());
            add(x, y);
            add(y, x);
        }

        num = 0;
        for (int i = 1; i <= n; i++) {
            if(dfn[i] == 0) { // 如果当前点没有被访问就开始寻找割点
                root = i; // 起始点为根节点
                targan(i);
            }
        }

        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if(cnt[i] == 1) ans++;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= n; i++) {
            if(cnt[i] == 1) sb.append(i+" ");
        }
        System.out.println(ans);
        System.out.println(sb.toString());
    }

    // 链式向前星边的实例
    static class Edge {
        int to;
        int next;

        public Edge(int to, int next) {
            this.to = to;
            this.next = next;
        }
    }

    // dfn 存储无向图的访问顺序的编号
    // low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)
    // cnt[u]用来记录u点是割点
    // head 链式向前星的数据结构数组
    private static int[] dfn, low, cnt, head;

    // root 全局根节点,用来判断根节点是不是割点
    // num 访问顺序编号全局变量
    // n 图中点的个数
    // m 图中边的个数
    // numb 链式向前星边的编号
    private static int root, num, n, m, numb;
    private static Edge[] edges;
    private static int maxn = 100010, maxm = 500010;

    // 添加边
    private static void add(int u, int v) {
        edges[numb] = new Edge(v, head[u]);
        head[u] = numb++;
    }

    public static void main(String[] args) throws Exception {
        new Thread(null, new Main(), "", 1 << 29).start();
    }

    private static void targan(int x) {
        dfn[x] = low[x] = ++num; // 访问顺序初始化时 dfn和low一样
        int count = 0; // 计算跟节点的分支

        // 链式向前星遍历边的编号
        for (int n = head[x]; n != -1 ; n = edges[n].next) {
            int i = edges[n].to;

            if(dfn[i] == 0) { // 没有被访问
                count++;     // 根节点分支加一
                targan(i);  // 深度搜索
                // 找到最底层回溯进行更新当前x点的low值,当前点low和回溯子节点low取小
                low[x] = Math.min(low[x], low[i]);
                // 如果x是根节点,并且 根节点的分支大于1 说明根节点是割点,进行标记
                // 如果x不是根节点,但是x的dfn小于子节点的 low值, 说明走向子节点i必须经过父节点x,所以父节点是割点
                if((x == root && count > 1) || (x != root && dfn[x] <= low[i])) {
                    cnt[x] = 1;
                }
            }
            // 如果找到最底部已经被访问过,更新当前点的low通过回溯边的 dfn比较
            low[x] = Math.min(low[x], dfn[i]);
        }
    }
}
复制代码

 

 

posted @   姓蜀名黍  阅读(156)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示