先讲一下 ,双联通分量 一定是用于 无向图
考虑什么时候需要用边双联通分量呢?,考虑给你的是一个一般图,需要你把联通的点都缩起来,视作一个点的情况,就是说割点可以反复访问,就是说割点和其他点都是等效的,并没有区别,,(正常情况都是边双联通分量,如果出到点双连通分量,一般难度很高)
点双联通分量使用时,通常,有些点有些特殊,比如,你通过一个环,必须得从一个点入,另一个点出,像下面图,无法通过这个环从1抵达6,这种的情况就需要用点双联通分量缩点
再点双之中,这种可以通过环来从1抵达6
但是在边双中两图完全没有区别
以后可以试试这两种走法是否有区别来判断使用点双还是边双
边双
/*
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define sqrt(a) sqrtl(a)
#define abs(a) llabs(a)
#define pow(a) powl(a)
typedef pair<int,int> pi ;
#define if1(x) for(int i =1 ;i<=x;i++)
#define if0(x) for(int i = 0;i<x;i++)
#define jf0(x) for(int j = 0;j<x;j++)
#define jf1(x) for(int j = 1;j<=x;j++)
#define pb push_back
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const int N = 2e5+10;
vector<pair<int,int>> edg[N];//终点和边的编号
vector<int> nedg[N];//新边图
int id[N];
int dfn[N],low[N],tim;
stack<int> stk;
vector<vector<int> > dcc;//边双连通分量
bool vis[N];
int cnt,idx;
int n,m;//点数目,边数目
int nidx ;//新点的编号
void tarjan(int u,int in_edg)
{
dfn[u] = low[u] = ++tim;//更新时间戳
stk.push(u);vis[u] = 1;
for(auto &j:edg[u]){
if(j.second ==(in_edg^1))continue;//是回边,则不走
if(!dfn[j.first]){//没有访问
tarjan(j.first,j.second);
low[u] = min(low[u],low[j.first]);
}else if(vis[j.first])
low[u] = min(low[u],low[j.first]);
}
if(dfn[u] == low[u]){//桥边到了
vector<int>te;
while(1){
int y = stk.top();stk.pop();
te.push_back(y);
vis[y] = 0;
if(u == y)break;
}dcc.push_back(te);
}
}
void suodian(){
for(auto j:dcc){
nidx++;
for(auto k:j)id[k] = nidx;//标记点的联通 块属于谁
}
if1(n){
for(auto [a,b]:edg[i]){
if(id[i]!=id[a]){
nedg[id[i]].push_back(id[a]);
}
}
}
}
void solve(){
cin>>n>>m;
idx = 0;
if0(m){
int a,b;
cin>>a>>b;
edg[a].push_back({b,idx++});
edg[b].push_back({a,idx++});
}
if1(n){
if(!dfn[i])tarjan(i,-1);
}
suodian();
if1(n)cout<<i<<" "<<id[i]<<"||";
cout<<endl;
//输出新图
if1(nidx){
cout<<i<<":";
for(auto j:nedg[i])cout<<j<<" ";
cout<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
// cin>>t;
while (t--)
{
solve();
}
return 0;
}
/*
9 11
1 2
2 3
3 1
1 4
1 5
5 6
4 7
1 7
2 7
5 8
9 6
输出
初始边和新边
1 5||2 5||3 5||4 5||5 4||6 2||7 5||8 3||9 1||
新图
1:2
2:4 1
3:4
4:5 2 3
5:4
*/
点双
/*
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define sqrt(a) sqrtl(a)
#define abs(a) llabs(a)
#define pow(a) powl(a)
typedef pair<int,int> pi ;
#define if1(x) for(int i =1 ;i<=x;i++)
#define if0(x) for(int i = 0;i<x;i++)
#define jf0(x) for(int j = 0;j<x;j++)
#define jf1(x) for(int j = 1;j<=x;j++)
#define pb push_back
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
const int N = 2e5+10;
vector<int> edg[N];//终点和边的编号
set<int> nedg[N];
int dfn[N],low[N],tim;
stack<int> stk;
vector<int > dcc[N];//点双连通分量
bool cut[N];//割点
int id[N];
int cnt,idx,root,nidx;
int n,m;//点数目,边数目
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;//更新时间戳
stk.push(u);
if(!edg[u].size()){//孤立点
dcc[++cnt].push_back(u);return;
}
int child = 0;
for(auto j:edg[u]){
if(!dfn[j]){
tarjan(j);
low[u] = min(low[u],low[j]);
if(low[j] >= dfn[u]){
child++;
if(u!=root||child>1){
cut[u] = 1;//是割点
}
cnt++;
while(1){
int z = stk.top();stk.pop();
dcc[cnt].push_back(z);
if(z == j)break;
}
dcc[cnt].push_back(u);
}
}
else{
low[u] = min(low[u],dfn[j]);
}
}
}
void suodian(){
nidx = cnt;
if1(n*m){
if(cut[i]){
id[i] = ++nidx;//给割点重新编号
}
}
if1(cnt){
// cout<<i<<":";
for(auto j:dcc[i]){
if(cut[j] == 1){
nedg[i].insert(id[j]);
nedg[id[j]].insert(i);
}
//割点已经编好新下标了
else id[j] = i;//以连通块序号来对块中顶点编号
// cout<<j<<" "<<id[j]<<",";
}
// cout<<endl;
}
}
void solve(){
cin>>n>>m;
if0(m){
int a,b;
cin>>a>>b;
edg[a].push_back(b);
edg[b].push_back(a);
}
if1(n){
if(!dfn[i])root = i,tarjan(i);
}
suodian();
if1(n)cout<<i<<" "<<id[i]<<"||";
cout<<endl;
//输出新图
if1(nidx){
cout<<i<<":";
for(auto j:nedg[i])cout<<j<<" ";
cout<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
// cin>>t;
while (t--)
{
solve();
}
return 0;
}
/*
8 10
1 2
3 4
2 3
2 4
1 4
1 5
5 6
5 7
5 8
7 8
*/