今天也是小小的学了一个tarjan算法中的割边的一个应用
他和割点很像,都是用来处理无向图的,只不过是不能走反向边罢了
我们首先来说一个割边的定义
割边
当我们在无向图中删除一个边,无向图被分成不联通的两部分,那么这条边就是割边
我们来看一下割边的模版代码的注释
1.变量名和数组
vector<pair<int,int>> edge;//边集
vector<int> e[200005];//每个点的出边集
int dfn[200005];//每个点的深搜序标记
int low[200005];//每个点的回溯值
int len;
vector<pair<int,int>> brige;//统计割边的数组
2.加边函数代码
void add(int v,int u)
{
edge.push_back({v,u});
e[v].push_back(edge.size()-1);//边的序号是从0开始,可以通过异或1查看是否是反边
}
3.tarjan函数代码
void tarjan(int v,int from)//from表示边来源的序号
{
dfn[v]=low[v]=++len;//给每个点打上标记
for(int xu:e[v])//去寻找当前点出边的序号
{
int u=edge[xu].second;//寻找当前点的子节点
if(dfn[u]==0)//如果没有访问过
{
tarjan(u,xu);
low[v]=min(low[v],low[u]);
if(low[u]>dfn[v])//是割边
{
brige.push_back({v,u});
}
}
else if(xu!=(from^1))//查询过并且不是反边
{
low[v]=min(low[v],dfn[u]);
}
}
}
例题
T103481 【模板】割边
思路:割边的板题,直接写就OK了,用上面的板子即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int u,v;
vector<pair<int,int>> edge;//边集
vector<int> e[200005];//每个点的出边集
int dfn[200005];
int low[200005];
int len;
vector<pair<int,int>> brige;
int cnt;
void add(int v,int u)
{
edge.push_back({v,u});
e[v].push_back(edge.size()-1);
}
void tarjan(int v,int from)//from表示边来源的序号
{
dfn[v]=low[v]=++len;
for(int xu:e[v])
{
int u=edge[xu].second;
if(dfn[u]==0)
{
tarjan(u,xu);
low[v]=min(low[v],low[u]);
if(low[u]>dfn[v])
{
brige.push_back({v,u});
}
}
else if(xu!=(from^1))
{
low[v]=min(low[v],dfn[u]);
}
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(dfn[i]==0)
tarjan(i,-1);
}
cout<<brige.size();
return 0;
}
P1656 炸铁路
思路:也是割边的模版题,只不过需要对其进行排序即可,反正用的也是pair直接排序就好了,不需要重载
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int u,v;
vector<pair<int,int>> edge;
vector<int> e[200005];
int dfn[200005];
int low[200005];
int len;
vector<pair<int,int>> brige;
void add(int v,int u)
{
edge.push_back({v,u});
e[v].push_back(edge.size()-1);
}
void tarjan(int v,int from)
{
dfn[v]=low[v]=++len;
for(int xu:e[v])
{
int u=edge[xu].second;
if(dfn[u]==0)
{
tarjan(u,xu);
low[v]=min(low[v],low[u]);
if(low[u]>dfn[v])
{
brige.push_back({v,u});
}
}
else if(xu!=(from^1))
{
low[v]=min(low[v],dfn[u]);
}
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(dfn[i]==0)
tarjan(i,-1);
}
sort(brige.begin(),brige.end());
for(auto u:brige)
{
cout<<u.first<<" "<<u.second<<"\n";
}
return 0;
}
P7687 [CEOI2005] Critical Network Lines
思路:唯一不同的地方就是要去维护一个子树内的a的网络的数量或者b网络的数量,并且所有关键路径一定都是割边,我们只需要找到割边里面的满足子节点里面包括a的数量是0或者k,b的数量为0或者l的即可
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct Node {
int dfn, low, suma, sumb;
};
int n, m, k, l;
int u, v;
vector<pair<int, int>> edge;
vector<int> e[MAXN];
vector<pair<int, int>> bridge;
Node nodes[MAXN];
int len;
void addEdge(int v, int u) {
edge.emplace_back(v, u);
e[v].push_back(edge.size() - 1);
}
void tarjan(int v, int from) {
nodes[v].dfn = nodes[v].low = ++len;
for (auto xu : e[v]) {
int u = edge[xu].second;
if (nodes[u].dfn == 0) {
tarjan(u, xu);
nodes[v].low = min(nodes[v].low, nodes[u].low);
nodes[v].suma += nodes[u].suma;
nodes[v].sumb += nodes[u].sumb;
if (nodes[u].low > nodes[v].dfn && ((nodes[u].suma == 0 || nodes[u].sumb == 0) || (nodes[u].suma == k || nodes[u].sumb == l))) {
bridge.emplace_back(v, u);
}
} else if (xu != (from ^ 1)) {
nodes[v].low = min(nodes[v].low, nodes[u].dfn);
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> k >> l;
for (int i = 1; i <= k; i++) {
cin >> u;
nodes[u].suma = 1; // 关键节点的计数
}
for (int i = 1; i <= l; i++) {
cin >> u;
nodes[u].sumb = 1; // 另外一类关键节点的计数
}
for (int i = 1; i <= m; i++) {
cin >> u >> v;
addEdge(u, v);
addEdge(v, u);
}
for (int i = 1; i <= n; i++) {
if (nodes[i].dfn == 0) {
tarjan(i, -1);
}
}
cout << bridge.size() << "\n";
for (const auto& br : bridge) {
cout << br.first << " " << br.second << "\n";
}
return 0;
}