耳分解、双极定向和 P9394 Solution
耳分解
设无向图 \(G'(V',E')\subset G(V,E)\),简单路径或简单环 \(P:x_1\to \dots \to x_k\) 被称为 \(G\) 关于 \(G'\) 的耳,当且仅当其满足 \(x_1,x_k\in V',x_2,x_3\dots x_{k-1}\not\in V'\)。如果 \(P\) 是简单路径,那么 \(P\) 称为开耳。
下面记树上 \(x,y\) 之间的路径为 \(P(x,y)\)。
一个无向连通图的一个连通子图序列 \((G_0,G_1,\dots G_k),G_i(V_i,E_i)\) 满足:
- \(G_0\) 是简单环。
- \(G_{i-1}\subset G_i\)。
- \(E_i-E_{i-1}\) 是 \(G_{i-1}\) 关于 \(G_i\) 的一个耳(开耳)。
那么称 \((G_0,G_1,\dots G_k)\) 是一个 \(G\) 的耳分解(开耳分解)。
无向连通图 \(G\) 存在耳分解当且仅当其边双连通,\(G\) 存在开耳分解当且仅当其点双连通。
证明:
只证明前一个。开耳分解类似。
耳分解 \(\Rightarrow\) 边双连通。显然,增加一个耳不会改变边双连通性。
边双连通 $\Rightarrow $ 耳分解。这就是耳分解的构造算法。
- 先求出 \(1\) 为根的 dfs 树(不存在横叉边!!)。找到一个非树边 \(1\to x\)。初始 \(G_0\) 设为 \(1\to x\) 和 \(P(1,x)\) 构成的简单环。
- 设已经构造了 \(G_i\),找到一个点 $x $ 的父亲 \(y\) 在 \(G_i\) 中,自己不在。找到他子树的返祖边 \(v\to u\),那么当前的耳就是 \(P(y,v)\cup (v\to u)\)。
- 重复上述过程直到点集为 \(V\),剩下的一个边就是一个耳。
双极定向
对于无向图 \(G(V,E)\) 和 \(s,t\in V,s\neq t\)。以下四个命题等价:
- 添加 \(s\to t\) 后 \(G\) 点双连通。
- 圆方树上的所有方点成链。并且 \(P(s,t)\) 是圆方树的直径。
- 存在 DAG \(G'\) 以 \(G\) 为基图,且 \(s\) 是唯一入度为 \(0\) 的点,\(t\) 是唯一出度为 \(0\) 的点。
- 存在 \(p\in S_n\),\(p_1=s,p_n=t\),任意前缀后缀的导出子图连通。
\(1,2\) 显然等价。
\(3\Rightarrow 4\):取定向后的拓扑排序即可。
\(4\Rightarrow 3\):当作拓扑排序定向即可。
\(1\Rightarrow 3\):在加开耳的过程中搞一下即可。
\(4\Rightarrow 1\):设存在(加边后)割点 \(u\),此时 \(s,t\) 在同一连通块。设另一任意连通块为 \(S\)。设 \(S\cup \{u\}\) 在 \(p\) 上最早晚的分别是 \(x,y\),则 \(x,y\) 至少有一个不是 \(u\)。此时根据 \(4\),\(S\) 应该和 \(s,t\) 连通块连通,矛盾。
如何构造 \(p\)(这样也构造了双极定向)?跑出 dfs 树(这些过程中不考虑加的 \(s\to t\) 边),记录每个点的 low。把每个点 \(u\) 按照先 dfs 儿子再操作自己的顺序,如果 \(u\not\in P(s,t)\),把 \(low_u\) 和 \(fa_u\) 结点的队列添加进 \(u\)。
接下来按顺序遍历 \(P(s,t)\),遍历到一个点就把他加进 \(p\),然后递归遍历其队列元素(如果没有被遍历过)。具体可以参见代码。这样做的正确性是不难发现的。
Solution
题意:可以把若干个点缩成一个点(不限次数),使得上述条件 \(4\) 被满足。最小化缩之后的点包含原来的点的个数的最大值。输出方案。
依靠前面的结论,就是希望圆方树的方点是链。这样不妨先考虑知道 \(s,t\) 怎么做。在这样的链上,圆点必须被缩成一个点,方点连接到的(不在链上的)圆点必须被缩成一个点。对这些取 \(\max\) 就是要求的答案。
有结论:固定一个点 \(u\),另一个点 \(v\) 在其子树内选取的话,最优的 \(v\) 是不断走向任意重儿子得到的结果(笔者写代码的时候没有注意到任意)。这个结论正确,因为不走向重儿子,在 \(u\) 上的影响都已经大于任何儿子内部的所有贡献了。
这个结论主要说明了:我们可以进行类似于贪心的过程,不会发生走向轻儿子(非当前最优)会导致总结果更优的情况。
那么可以在树上进行 dp。先求出每个点一直向重儿子走的贡献 \(f\),枚举选取的 \(s,t\) 的 LCA,取其重儿子和次重(非严格)儿子更新答案即可。
求出 \(s,t\) 之后,按上面的构造方法来即可。
下面的代码是可以被精简的。
// Problem: P9394 白鹭兰
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P9394
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e5+5,INF=1e9;
int dfn[maxn],st[maxn],tp=0,T=0,cnt=0,low[maxn],n,m;
vector<int> e[maxn],e2[maxn];
void tarjan(int u){
dfn[u]=low[u]=++T;
st[++tp]=u;
for(auto v:e[u]){
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(dfn[u]==low[v]){
++cnt;
for(int x=0;x!=v;--tp){
x=st[tp];
e2[x].push_back(cnt);
e2[cnt].push_back(x);
}
e2[u].push_back(cnt);
e2[cnt].push_back(u);
}
}else low[u]=min(low[u],dfn[v]);
}
}
int sze[maxn],f[maxn],g[maxn],msze[maxn],msze2[maxn],d[maxn];
void dfs1(int u,int fa){
sze[u]=(u<=n);
for(auto v:e2[u])if(v!=fa)dfs1(v,u),sze[u]+=sze[v];
for(auto v:e2[u]){
if(v==fa)continue;
if(sze[v]>msze[u])msze2[u]=msze[u],msze[u]=sze[v];
else if(sze[v]>msze2[u])msze2[u]=sze[v];
}
}
struct qsy{
int a,b,c;
};
bool operator <(qsy x,qsy y){
return make_pair(x.a,-x.b)<make_pair(y.a,-y.b);
}
void calcf(int u,int fa){
bool cld=0;
qsy ans={0,INF,-1};
for(auto v:e2[u]){
if(v==fa)continue;
calcf(v,u);
cld=1;
if(u<=n)ans=max(ans,(qsy){sze[v],max(f[v],sze[u]-sze[v]),d[v]});
else ans=max(ans,(qsy){sze[v],max(f[v],sze[v]==msze[u]?msze2[u]:msze[u]),d[v]});
}
if(cld==0)f[u]=sze[u],d[u]=u;
else f[u]=ans.b,d[u]=ans.c;
}
int mn=INF,minx,miny;
void upd(int ans,int x,int y){
if(x>n||y>n)return ;
if(ans<mn)mn=ans,minx=x,miny=y;
}
int bcnt[maxn];
void dp(int u,int fa){
qsy m1={0,INF,-1},m2={0,INF,-1};
for(auto v:e2[u]){
if(v==fa)continue;
dp(v,u);
qsy cur={sze[v],f[v],d[v]};
if(m1<cur)m2=m1,m1=cur;
else if(m2<cur)m2=cur;
}
int ans=0;
if(m2.b==INF){
if(m1.b==INF){return ;}
else ans=m1.b;
}else ans=max(m1.b,m2.b);
bcnt[m1.a]++,bcnt[m2.a]++;
int res=0;
for(auto v:e2[u]){
if(v==fa)continue;
if(bcnt[sze[v]]>0)bcnt[sze[v]]--;
else{
if(u<=n)res+=sze[v];
else res=max(res,sze[v]);
}
}
if(u<=n)res+=n-sze[u]+1;
else res=max(res,n-sze[u]);
ans=max(ans,res);
g[u]=ans;int X=m1.c,Y=m2.c;
if(Y==-1)Y=u;
upd(g[u],X,Y);
}
int ban1,ban2,c[maxn],K,C;
void dfs2(int u,int fa){
st[++tp]=u;
if(u==miny){
K=tp;
for(int i=1;i<=K;i++)c[i]=st[i];
return ;
}
for(auto v:e2[u]){
if(v==fa)continue;
dfs2(v,u);
}
--tp;
}
vector<int> pt[maxn];
int bel[maxn];
void dfs3(int u,int fa){
if(u==ban1||u==ban2)return ;
if(u<=n)pt[cnt].push_back(u);
bel[u]=cnt;
for(auto v:e2[u]){
if(v==fa)continue;
dfs3(v,u);
}
}
vector<int> e3[maxn],L[maxn],e4[maxn];
bool vis[maxn],vis2[maxn];
int Ans[maxn],len=0,hzc[maxn],mk=0,Fa[maxn],rev[maxn];
void tarjan2(int u){
dfn[u]=low[u]=++T;
rev[dfn[u]]=u;
st[++tp]=u;
if(u==miny)for(int i=1;i<=tp;i++)vis2[st[i]]=1,hzc[++mk]=st[i];
for(auto v:e3[u]){
if(!dfn[v]){
e4[u].push_back(v);
e4[v].push_back(u);
Fa[v]=u;
tarjan2(v);
low[u]=min(low[u],low[v]);
}else low[u]=min(low[u],dfn[v]);
}
--tp;
}
void dfs4(int u){
if(vis[u])return ;
Ans[++len]=u;
vis[u]=1;
for(auto v:L[u])dfs4(v);
}
namespace checker{
vector<int> cur;
bool vis[maxn],vis2[maxn];
void dfs(int u){
if(vis[u])return;
vis[u]=1;
for(auto v:e[u]){
if(!vis2[v])continue;
dfs(v);
}
}
bool check(){
for(int i=1;i<=n;i++)vis[i]=vis2[i]=0;
for(auto u:cur)vis2[u]=1;
dfs(cur[0]);
for(auto u:cur)if(!vis[u])return 0;
return 1;
}
void checkans(){
for(int i=1;i<=cnt;i++){
for(auto u:pt[Ans[i]])cur.push_back(u);
if(!check())exit(1);
}
cur.clear();
for(int i=cnt;i>=1;i--){
for(auto u:pt[Ans[i]])cur.push_back(u);
if(!check())exit(1);
}
}
}
void dfs5(int u,int fa){
for(auto v:e4[u]){
if(v==fa)continue;
dfs5(v,u);
}
if(!vis2[u])L[Fa[u]].push_back(u),L[rev[low[u]]].push_back(u);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
cnt=n;
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
dfs1(1,0);calcf(1,0);dp(1,0);
tp=0;dfs2(minx,0);cnt=0;
for(int i=1;i<=K;i++){
ban1=c[i-1],ban2=c[i+1];
if(c[i]<=n){
C=++cnt;
dfs3(c[i],0);
}else{
for(auto v:e2[c[i]]){
if(v==ban1||v==ban2)continue;
C=++cnt;
dfs3(v,c[i]);
}
}
}
cout<<mn<<" "<<cnt<<endl;
for(int i=1;i<=n;i++)for(auto j:e[i])if(bel[i]!=bel[j])e3[bel[i]].push_back(bel[j]);
minx=bel[minx],miny=bel[miny],T=0;tp=0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
tarjan2(minx);
dfs5(minx,0);
for(int i=1;i<=mk;i++)dfs4(hzc[i]);
for(int i=1;i<=cnt;i++){
cout<<pt[Ans[i]].size()<<" ";
for(auto u:pt[Ans[i]])cout<<u<<" ";
cout<<endl;
}
// checker::checkans();
return 0;
}