割边
代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
vector<vector<int>> a;
vector<pair<int,int>> ans;
int n,m;
int dfn[155],cnt,low[155];
int dfs(int u,int fa) {
if(dfn[u]) return dfn[u];
low[u]=dfn[u]=++cnt;
for(auto&v:a[u])
if(v^fa) {
low[u]=min(low[u],dfs(v,u));
if(low[v]>dfn[u])
ans.push_back({min(u,v),max(u,v)});
}
return low[u];
}
void Tarjan() {
dfs(1,0);
}
int main() {
cin>>n>>m;
a.resize(n+1);
for(int i=1;i<=m;i++) {
int u,v;
cin>>u>>v;
a[u].push_back(v);
a[v].push_back(u);
}
Tarjan();
sort(ans.begin(),ans.end());
for(auto&i:ans)
cout<<i.first<<' '<<i.second<<endl;
}
然而并没有什么细节。
边双连通分量(e-DCC)
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int N=5e5;
int n,m;
int h[N],to[N*8+5],nxt[N*8+5],tot=1;
void add(int u,int v) {
nxt[++tot]=h[u];
to[tot]=v;
h[u]=tot;
}
stack<int> s;
int dfn[N+5],low[N+5],cnt;
vector<vector<int>> ans;
int num;
int dfs(int u,int pre) {
if(dfn[u]) return dfn[u];
low[u]=dfn[u]=++cnt;
s.push(u);
for(int i=h[u];i;i=nxt[i]) {
int v=to[i];
if(!dfn[v])
low[u]=min(low[u],dfs(v,i));
//low[v]>dfn[u]是割边
else if(i^pre^1)
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]) {
while(!s.empty()) {
int x=s.top();
s.pop();
ans[u].push_back(x);
if(u==x) break;
}
num++;
}
return low[u];
}
void Tarjan() {
for(int i=1;i<=n;i++)
if(!dfn[i])
dfs(i,1);
}
int main() {
cin>>n>>m;
ans.resize(n+1);
for(int i=1;i<=m;i++) {
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
Tarjan();
cout<<num<<endl;
for(int i=1;i<=n;i++)
if(ans[i].size()) {
cout<<ans[i].size()<<' ';
for(auto&j:ans[i])
cout<<j<<' ';
cout<<endl;
}
return 0;
}
- 由于要快速访问正反边,因此难以使用vector数组存边,这里使用链式前向星存边。
- 注意dfs中的i^pre ^1,指的是不能通过反边pre ^1回到节点的父亲,可以写成i!=pre ^1
点双连通分量(v-DCC)
割点、点双连通分量,在Tarjan中较复杂。
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int N=5e5;
vector<vector<int>> a;
vector<vector<int>> ans;
stack<int> s;
int n,m;
int dfn[N+5],low[N+5],cnt;
bool cut[N+5];
int root;
int dfs(int u,int fa) {
if(dfn[u]) return dfn[u];
s.push(u);
low[u]=dfn[u]=++cnt;
if(u==root&&!a[u].size()) {
ans.push_back({u});
cut[u]=true;
return low[u];
}
int son=0;
for(auto&v:a[u])
if(!dfn[v]) {
son++;
low[u]=min(low[u],dfs(v,u));
if(low[v]>=dfn[u]) {
int k=ans.size();
ans.push_back({});
if(root^u||k>1) cut[u]=true;
while(!s.empty()) {
int x=s.top();
s.pop();
ans[k].push_back(x);
if(x==v) break;
}
ans[k].push_back(u);
}
} else if(v^fa)
low[u]=min(low[u],dfs(v,u));
return low[u];
}
void Tarjan() {
for(root=1;root<=n;root++)
if(!dfn[root])
dfs(root,0);
}
int main() {
cin>>n>>m;
a.resize(n+1);
for(int i=1; i<=m; i++) {
int u,v;
cin>>u>>v;
if(u==v) continue;
a[u].push_back(v);
a[v].push_back(u);
}
Tarjan();
cout<<ans.size()<<endl;
for(auto&i:ans)
if(i.size()) {
cout<<i.size()<<' ';
for(auto&j:i)
cout<<j<<' ';
cout<<endl;
}
return 0;
}
- 首先注意dfs的过程,要先特判一下孤立点。本题当中的自环,在读入的时候去除了。
- 然后注意,在dfs中,只要满足low[v]>=dfn[u]的条件,就是一个边双连通分量,至于u是不是割点,还需要再判断。
- 然后注意u在一些题中可能需要裂点,这样空间需要开到两倍(考虑一个菊花图)。当然本题当中使用嵌套vector来存答案,就不需要考虑内存的问题了。
后记
于是皆大欢喜。