割点模板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^41≤n≤2×104,1\leq m \le 10^51≤m≤105。
点的编号均大于 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]); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理