耳分解、双极定向和 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;
}
posted @ 2024-03-07 11:37  British_Union  阅读(58)  评论(0编辑  收藏  举报