耳分解与双极定向

耳分解

耳分解能极好地维护连通性问题。

很形象的说法,设最初的子图为 G0,原图为 GN,满足 G0 是一个边双连通分量。

每一次加一条“耳”,由图 Gi 转到图 Gi+1。“耳”就是一条形如 (x1x2x3...xk) 的路径,满足点 x1,xkGi1<i<k,xiGi

我们称 x1xk 的“耳”为开耳。

结论:

  1. 边双必然存在耳分解
  2. 点双必然存在开耳分解

证明只需在 dfs 树上操作即可得证。

例题:P5776(洛谷)


双极定向

一个问题:一张无向图 G=(V,E),将边定向得到一个 DAG(有向无环图),满足无入度的点只有一个,无出度的点也只有一个。

先考虑有解的条件。

我们先求出圆方树,可以得到:若方点连成一条链则定然有解。

构造我们从拓扑序入手,容易发现对于割点,一定是这个点双中拓扑序最后面的,于是我们以点双中在拓扑序中第一个出现的点为根建出 dfs 树。

然后我们考虑 tarjan 算法,对每个点维护两条边,一条连向父亲 f(x),一条连向深度最浅的与其有边的点 low(x)

于是我们就从下往上考虑,对于每个点 x 用 vector 维护一个后继列表 Lx,每次删一个叶子 u,就在 low(x)f(x) 处加入点 u,最后再 dfs 按顺序递归即可得到一组合法拓扑序。

例题:P9394(洛谷)

点击查看代码
#include<bits/stdc++.h>
#define fir first
#define sec second
#define int long long
#define lowbit(x) x&(-x)
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef pair<int,int> pir;
inline int read(){
int x=0,f=1; char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1; c=getchar();}
while(isdigit(c)){x=x*10+(c^48); c=getchar();}
return x*f;
}
const int inf=1e9,N=5e5+5;
int n,m,nd;
vector<int> ed[N],re[N];
int dfn[N],low[N],cnt;
int st[N],top;
inline void tarjan(int x){
st[++top]=x;
dfn[x]=low[x]=++cnt;
for(auto v:ed[x]){
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
if(dfn[x]==low[v]){
nd++;
re[nd].push_back(x);
re[x].push_back(nd);
for(;st[top+1]!=v;top--){
int p=st[top];
re[nd].push_back(p);
re[p].push_back(nd);
}
}
}
else low[x]=min(low[x],dfn[v]);
}
}
int siz[N],f[N],lf[N],ans;
int S,T;
inline void pre(int x,int fa){
siz[x]=(x<=n);
int mx=0,sec=0;
for(auto v:re[x]){
if(v==fa) continue;
pre(v,x);
siz[x]+=siz[v];
if(siz[mx]<siz[v]) sec=mx,mx=v;
else if(siz[sec]<siz[v]) sec=v;
}
int res=0;
if(x<=n){
res=max(res,n-siz[mx]-siz[sec]);
f[x]=siz[x]-siz[mx];
}
else{
for(auto v:re[x]){
if(v==fa||v==mx||v==sec) continue;
res=max(res,siz[v]);
}
f[x]=max(res,siz[sec]);
res=max(res,n-siz[x]);
}
res=max({res,f[mx],f[sec]});
f[x]=max(f[x],f[mx]);
lf[x]=(mx?lf[mx]:x);
if(res<ans){
ans=res;
if(mx) S=lf[mx];
else S=x;
if(sec) T=lf[sec];
else T=x;
}
}
int Fa[N];
int Dfn[N],sz[N],id[N],tot;
inline void dfs(int x,int fa){
Fa[x]=fa,sz[x]=1;
Dfn[x]=++tot,id[tot]=x;
for(auto v:re[x]){
if(v==fa) continue;
dfs(v,x);
sz[x]+=sz[v];
}
}
int dep[N],ff[N],vis[N],Low[N];
int mxdep;
int vv[N],pt[N];
vector<int> D[N],ad[N];
vector<vector<int> > res;
int P[N];
inline void init(int x,int fa){
ff[x]=fa;
vis[x]=1;
dep[x]=dep[fa]+1;
mxdep=max(mxdep,dep[x]);
for(auto v:ed[x]){
if(v==fa||!vv[v]) continue;
if(vis[v]){
if(dep[v]<dep[x]){
if(!Low[x]||dep[Low[x]]>dep[v]) Low[x]=v;
}
continue;
}
init(v,x);
if(!Low[x]||dep[Low[x]]>dep[Low[v]]) Low[x]=Low[v];
}
D[dep[x]].push_back(x);
}
inline void gtV(int x){
vector<int> seq;
if(!pt[x]){
for(int i=Dfn[x];i<Dfn[x]+sz[x];i++){
if(id[i]<=n)
seq.push_back(id[i]);
}
}
else{
seq.push_back(x);
for(auto v:re[x]){
if(pt[v]) continue;
for(int i=Dfn[v];i<Dfn[v]+sz[v];i++)
if(id[i]<=n) seq.push_back(id[i]);
}
}
res.push_back(seq);
}
inline void dfs2(int x){
gtV(x);
vis[x]=1;
for(auto v:ad[x]) if(!vis[v]) dfs2(v);
ad[x].clear();
}
inline void solve(int now,int st,int ed){
mxdep=0;
for(auto v:re[now]) vv[v]=1,vis[v]=0;
init(st,0);
vector<int> seq;
for(int t=ed;t;t=ff[t]) vis[t]=0,seq.push_back(t);
reverse(seq.begin(),seq.end());
for(int i=mxdep;i>=1;i--){
for(auto x:D[i]){
if(!vis[x]) continue;
vis[x]=0;
if(ff[x]) ad[ff[x]].push_back(x);
if(Low[x]) ad[Low[x]].push_back(x);
}
D[i].clear();
}
for(int t=ed;t;t=ff[t]) vis[t]=1;
for(auto x:seq){
if(x==st){
for(auto v:ad[x]) if(!vis[v]) dfs2(v);
ad[x].clear();
}
else dfs2(x);
}
for(auto v:re[now]) vv[v]=0;
}
signed main(){
nd=n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
ed[u].push_back(v);
ed[v].push_back(u);
}
tarjan(1);
ans=n,pre(1,0);
dfs(S,0);
gtV(T);
int lst=0;
for(int now=T;now;now=Fa[now]) pt[now]=1;
for(int now=T;now;now=Fa[now]){
if(now>n) solve(now,lst,Fa[now]);
lst=now;
}
int Mx=0;
cout<<ans<<' '<<res.size()<<'\n';
for(auto tmp:res){
cout<<tmp.size()<<' ';
Mx=max(Mx,(int)tmp.size());
for(auto v:tmp) cout<<v<<' ';
puts("");
}
}
</details>
posted @   ~Cyan~  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示