-
OI-Contest 荣耀徽章
该用户太菜了,一个徽章也没有 (´・ω・`) -
个人简介
Tarjan割边模板
#include<bits/stdc++.h> #define N 1000020 using namespace std; namespace zyt { const int maxn=5e4 + 5; struct edges{ int to,next; }edge[600005]; int cntx=0; int head[maxn]; void add(int x,int y) { edge[cntx].to=y; edge[cntx].next=head[x]; head[x] = cntx++; } int dfn[maxn],low[maxn]; bool bridge[600005]; int dcc[maxn],cnt=0,c=0; void tarjan(int x,int in_edge) { dfn[x]=low[x]=cnt++; for (int i=head[x]; i!=-1; i=edge[i].next) { int t=edge[i].to; if(!dfn[t]) { tarjan(t,i); low[x]=min(low[x],low[t]); if(low[t]>dfn[x]) { bridge[i]=bridge[i^1]=true; } }else if(i!=(in_edge^1)) low[x]=min(low[x],dfn[t]); } } void dfs(int x) { dcc[x] = c; for (int i=head[x];i!=-1; i=edge[i].next) { int y=edge[i].to; if(dcc[y]||bridge[i]) continue; dfs(y); } } int work() { int n,m; cin>>n>>m; memset(head,-1,sizeof(head)); for (int i=1;i<=m;i++) { int x,y; cin>>x>>y; if(x==y) continue; add(x,y),add(y,x); } for (int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i,i); } for (int i=1;i<=n;i++) { if(!dcc[i]) { c++; dfs(i); } } cout<<c<<endl; return 0; } } int main() { return zyt::work(); }
代码讲解
代码总览
该程序使用了 Tarjan算法 来找出图中的所有桥(Bridge),然后通过DFS遍历标记每个节点所属的边双连通分量,最后输出分量的总数。
代码详解
1. 头文件与全局定义
#include<bits/stdc++.h> #define N 1000020 using namespace std;
#include<bits/stdc++.h>
:包含所有标准库头文件,常见于竞赛编程中,但不推荐在生产环境中使用。#define N 1000020
:定义常量N
,表示最大节点数(但实际代码中使用了maxn=5e4+5
,所以这里可能未使用)。using namespace std;
:使用标准命名空间。
2. 命名空间 zyt
将主要逻辑封装在命名空间
zyt
中,避免全局变量污染。3. 图的结构定义
struct edges{ int to,next; }edge[600005];
- 使用链式前向星存储图结构。
to
:边的终点。next
:指向下一条边的索引。
4. 全局变量
int cntx=0; // 边计数器 int head[maxn]; // 头指针数组 int dfn[maxn]; // DFS序(时间戳) int low[maxn]; // 当前节点能够回溯到的最早访问的节点的时间戳 bool bridge[600005];// 标记某条边是否为桥 int dcc[maxn]; // 记录每个节点所属的边双连通分量编号 int cnt=0; // 时间戳计数器 int c=0; // 边双连通分量计数器
cntx
:用于记录边的数量,每次加边时递增。head
:存储每个节点的第一条边。dfn
:记录每个节点在DFS遍历中的顺序(时间戳)。low
:记录当前节点通过其子孙节点能回溯到的最小时间戳。bridge
:标记某条边是否为桥(即删除后图不再连通)。dcc
:记录每个节点属于哪个边双连通分量。cnt
:DFS时间戳。c
:边双连通分量的数量。
5. 加边函数
void add(int x,int y) { edge[cntx].to=y; edge[cntx].next=head[x]; head[x] = cntx++; }
- 标准的链式前向星加边操作,注意这里添加的是无向边,所以每条边会添加两次(正向和反向)。
6. Tarjan算法找桥
void tarjan(int x,int in_edge) { dfn[x]=low[x]=cnt++; for (int i=head[x]; i!=-1; i=edge[i].next) { int t=edge[i].to; if(!dfn[t]) { tarjan(t,i); low[x]=min(low[x],low[t]); if(low[t]>dfn[x]) { bridge[i]=bridge[i^1]=true; } } else if(i!=(in_edge^1)) low[x]=min(low[x],dfn[t]); } }
in_edge
:记录进入当前节点的边(防止重复访问父节点)。dfn[x] = low[x] = cnt++
:初始化当前节点的时间戳。- 遍历所有邻接节点:
- 如果未访问过(
!dfn[t]
),则递归调用tarjan
,并更新low[x]
。 - 如果满足
low[t] > dfn[x]
,说明边(x, t)
是桥(因为t
无法通过其他路径回到x
或更早的节点),标记该边及其反向边为桥。 - 如果邻接节点已访问过且不是当前边的反向边(防止直接回溯),则用其
dfn
更新low[x]
。
- 如果未访问过(
7. DFS标记边双连通分量
void dfs(int x) { dcc[x] = c; for (int i=head[x];i!=-1; i=edge[i].next) { int y=edge[i].to; if(dcc[y]||bridge[i]) continue; dfs(y); } }
- 遍历所有节点,如果节点还未被标记分量(
!dcc[y]
)且边不是桥,则继续DFS。 - 注意:桥不属于任何边双连通分量,所以遇到桥就跳过。
8. 主逻辑函数 work()
int work() { int n,m; cin>>n>>m; memset(head,-1,sizeof(head)); for (int i=1;i<=m;i++) { int x,y; cin>>x>>y; if(x==y) continue; add(x,y),add(y,x); } for (int i=1;i<=n;i++) { if(!dfn[i]) tarjan(i,i); } for (int i=1;i<=n;i++) { if(!dcc[i]) { c++; dfs(i); } } cout<<c<<endl; return 0; }
- 读入节点数
n
和边数m
。 - 初始化
head
为-1(链式前向星空表标志)。 - 读入边,忽略自环(
x==y
)。 - 对每个未访问的节点调用
tarjan
找桥。 - 再次遍历所有节点,如果未标记分量,则进行DFS标记该连通分量中的所有节点(跳过桥)。
- 输出边双连通分量的数量
c
。
9. main函数
int main() { return zyt::work(); }
- 直接调用
zyt::work()
并返回结果。
算法学习推荐
1. 链式前向星
- 介绍:一种高效的图的存储方式,尤其适用于稀疏图。
- 推荐学习网址:
2. Tarjan算法
- 介绍:用于求解图的强连通分量(有向图)、桥和割点(无向图)。
- 推荐学习网址:
3. 边双连通分量(e-DCC)
- 介绍:删除图中所有桥后,剩余的连通块即为边双连通分量。
- 推荐学习网址:
总结
该代码通过以下步骤求解边双连通分量:
- 使用链式前向星存储图。
- 应用Tarjan算法标记所有桥。
- 通过DFS(跳过桥)标记每个节点所属的边双连通分量。
- 统计分量的数量并输出。
注意:边双连通分量中不包含桥,且每个节点属于且仅属于一个边双连通分量。
作业第二题代码
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+5; struct graph{ int head[maxn],nxt[maxn],to[maxn],flag[maxn],cnt; inline graph():cnt(1){} inline void add(int u,int v,int w){ nxt[++cnt]=head[u]; to[cnt]=v; head[u]=cnt; flag[cnt]=w; } }gr,cgr; int n,m,dfn[maxn],low[maxn],st[maxn],color[maxn],idx,col,top,a,b; int f[maxn]; void tarjan(int u,int fa){ dfn[u]=low[u]=++idx; st[++top]=u; for(int i=gr.head[u];i;i=gr.nxt[i]){ int v=gr.to[i]; if(v==fa)continue; if(!dfn[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); }else if(!color[v]){ low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u]){ col++; while(st[top]!=u){ color[st[top]]=col; top--; } color[u]=col; top--; } } void dfs(int u,int fa){ for(int i=cgr.head[u];i;i=cgr.nxt[i]){ int v=cgr.to[i],w=cgr.flag[i]; if(v==fa)continue; f[v]|=f[u]|w; dfs(v,u); } } int main(){ ios::sync_with_stdio(false); cin>>n>>m; for(int i=1;i<=m;i++){ int u,v,w; cin>>u>>v>>w; gr.add(u,v,w); gr.add(v,u,w); } cin>>a>>b; for(int i=1;i<=n;i++){ if(!dfn[i])tarjan(i,0); } int tot=0; for(int u=1;u<=n;u++){ for(int i=gr.head[u];i;i=gr.nxt[i]){ tot++; int v=gr.to[i],w=gr.flag[i]; if(color[u]==color[v]){ f[color[u]]|=w; }else{ //注意这里存图的边数要乘4倍 cgr.add(color[u],color[v],w); cgr.add(color[v],color[u],w); } } } dfs(color[a],0); if(f[color[b]]){ cout<<"YES"<<endl; }else{ cout<<"NO"<<endl; } return 0; }
代码解释与算法学习推荐
代码功能概述
这段代码解决的是无向图中两点间路径存在问题:给定一个无向图,每条边有一个权重(0或1),判断从节点a到节点b是否存在至少一条路径,该路径上至少有一条边的权重为1。
代码结构分析
1. 图结构定义
struct graph{ int head[maxn], nxt[maxn], to[maxn], flag[maxn], cnt; inline graph():cnt(1){} inline void add(int u, int v, int w){ nxt[++cnt] = head[u]; to[cnt] = v; head[u] = cnt; flag[cnt] = w; } } gr, cgr;
- 使用链式前向星存储图结构
gr
存储原图,cgr
存储缩点后的图flag
数组存储边的权重(0或1)
2. Tarjan算法实现
void tarjan(int u, int fa){ dfn[u] = low[u] = ++idx; st[++top] = u; for(int i = gr.head[u]; i; i = gr.nxt[i]){ int v = gr.to[i]; if(v == fa) continue; if(!dfn[v]){ tarjan(v, u); low[u] = min(low[u], low[v]); } else if(!color[v]){ low[u] = min(low[u], dfn[v]); } } if(dfn[u] == low[u]){ col++; while(st[top] != u){ color[st[top]] = col; top--; } color[u] = col; top--; } }
- 用于寻找无向图的边双连通分量
dfn
记录访问时间戳,low
记录能回溯到的最早节点- 通过栈管理节点,发现强连通分量时出栈并染色
3. DFS遍历
void dfs(int u, int fa){ for(int i = cgr.head[u]; i; i = cgr.nxt[i]){ int v = cgr.to[i], w = cgr.flag[i]; if(v == fa) continue; f[v] |= f[u] | w; dfs(v, u); } }
- 在缩点后的图上进行深度优先搜索
- 传播标记信息
f
,记录从起点到当前节点的路径上是否有权重为1的边
4. 主函数流程
- 读入节点数n和边数m
- 构建原图
- 读入起点a和终点b
- 使用Tarjan算法进行边双连通分量缩点
- 构建缩点后的图:
- 同一分量内的边权重进行"或"操作
- 不同分量间保留边及其权重
- 从起点所在分量开始DFS
- 检查终点所在分量的标记情况
算法学习推荐
1. Tarjan算法
- 用途:寻找有向图或无向图的强连通分量/边双连通分量
- 学习资源:
2. 图论基础
3. 深度优先搜索(DFS)
4. 竞赛编程平台
- 实践平台:
代码优化建议
- 数组大小:当前
maxn=1e6+5
可能不足以处理大规模数据,应根据题目要求调整 - 内存管理:考虑使用vector代替静态数组,提高灵活性
- 算法效率:Tarjan算法的时间复杂度为O(V+E),适合大规模图处理
- 代码可读性:添加更多注释,特别是算法关键步骤的说明
这段代码展示了图论中常见的缩点技术,通过将图简化为DAG(有向无环图),从而简化路径查询问题,是解决复杂图论问题的有效方法。
-
证书
该用户太菜了,一本证书也没有 (´・ω・`) -
AC题目
- P1001
- P1026
- P1032
- P2941
- P1055
- P1053
- P1065
- P1066
- P1070
- P1067
- P1077
- P1087
- P1089
- P1090
- P1091
- P1112
- P1114
- P1095
- P1096
- P1125
- P1145
- P1149
- P1161
- P1162
- P1185
- P1186
- P1187
- P1190
- P1191
- P1253
- P1245
- P1265
- P1247
- P1290
- P1297
- P1324
- P1355
- P1428
- P1441
- 447(隐藏)
- P1423
- P1425
- P1443
- P1452
- P1453
- P1444
- P1463
- P1445
- P1451
- P1506
- P1494
- P1497
- P1498
- P1499
- P1500
- P1524
- P1564
- P1549
- P1592
- P1593
- P1655
- P1657
- P1660
- P1681
- P1683
- P1685
- P1669
- P1670
- P1695
- P1705
- P1700
- P1706
- P1739
- P1741
- P1744
- P1753
- P1750
- P1751
- P1773
- P1766
- P1770
- P1771
- P1772
- P1758
- P1761
- P1785
- P1788
- P1790
- P1791
- P1795
- P1796
- P1798
- P1799
- P1800
- P1813
- P1809
- P1814
- P1775
- P1826
- P1832
- P1833
- P1839
- P1840
- P1848
- P1862
- P1871
- P1901
- P1926
- P1920
- P1913
- P1699
- P1967
- P1994
- P1993
- P2031
- P1997
- P2018
- P2022
- P2023
- P2055
- P2064
- P2067
- P2072
- P2079
- P2090
- P2120
- P2142
- P2178
- P2188
- P2190
- P2194
- P2202
- P2205
- P2230
- P2232
- P2238
- P2248
- P2253
- P2255
- P2258
- P2259
- P2274
- P2286
- P2289
- P2293
- P2294
- P2295
- P2296
- P2300
- P2301
- P2302
- P2303
- P2304
- P2312
- P2313
- P2316
- P2321
- P2322
- P2347
- P2369
- P2396
- P2406
- P2488
- P2587
- P2433
- P2461
- P2513
- P2519
- P2520
- P1147
- P2650
- CSPJ202002
- P2670
- P2668
- P2735
- P2245
- P2751
- P2752
- 1898(隐藏)
- P2799
- P2811
- 2123(隐藏)
- 2336(隐藏)
- P3095
- 2454(隐藏)
- 2479(隐藏)
- 2481(隐藏)
- 2487(隐藏)
- 2509(隐藏)
- 2713(隐藏)
- 2755(隐藏)
- P3276
- P3298
- P3300
- P3299
- 2881(隐藏)
- 2885(隐藏)
- 2886(隐藏)
- 2887(隐藏)
- 2888(隐藏)
- 2889(隐藏)
- 2890(隐藏)
- 2891(隐藏)
- MNS303
- MNS305
- 2897(隐藏)
- MNS311
- MNS312
- MNS313
- MNS314
- MNS315
- MNS316
- MNS317
- MNS318
- 2949(隐藏)
- 2951(隐藏)
- 2971(隐藏)
- 2984(隐藏)
- 2994(隐藏)
- 3056(隐藏)
- 3064(隐藏)
- 3273(隐藏)
- 3275(隐藏)
Problem Tags
- 动态规划
- 12
- 贪心
- 7
- 前缀和
- 6
- 树
- 6
- 最短路
- 6
- 二分
- 6
- 搜索
- 6
- 并查集
- 6
- gcd
- 5
- 其他
- 5
- 计算
- 5
- 数学
- 5
- KMP
- 5
- 字符串
- 4
- 排序
- 4
- 递归
- 4
- 图论
- 4
- dfs
- 4
- 快速幂
- 4
- 差分
- 4