耳分解、双极定向和 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;
}
耳分解、双极定向和 P9394 Solution的更多相关文章
- Solution -「Gym 102759C」Economic One-way Roads
\(\mathcal{Description}\) Link. 给定一个含 \(n\) 个点 \(m\) 条边的简单无向图,每条边的两种定向方法各有权值,求使得图强连通且定向权值和最小的方法. ...
- Image Processing and Analysis_8_Edge Detection:Theory of Edge Detection ——1980
此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...
- VCC、VDD、VSS、 VEE 和VPP的区别
在电子电路中,常可以看到VCC.VDD和VSS三种不同的符号,它们有什么区别呢? 一.解释 VCC:C=circuit 表示电路的意思, 即接入电路的电压: VDD:D=device 表示器件的意思, ...
- C、VDD、VSS、 VEE 和VPP的区别
http://www.cnblogs.com/crazybingo/archive/2010/05/14/1735802.html C.VDD.VSS. VEE 和VPP的区别 在电子电路中,常可以看 ...
- 液晶常用接口“LVDS、TTL、RSDS、TMDS”技术原理介绍
液晶常用接口“LVDS.TTL.RSDS.TMDS”技术原理介绍 1:Lvds Low-Voltage Differential Signaling 低压差分信号 1994年由美国国家半导体公司提出之 ...
- VCC,VDD,VEE,VSS,VPP 表示的意义
转自VCC,VDD,VEE,VSS,VPP 表示的意义 VCC,VDD,VEE,VSS,VPP 表示的意义 版本一: 简单说来,可以这样理解: 一.解释 VCC:C=circuit 表示电路的意思, ...
- VCC、VDD、VEE、VSS
转载自:http://www.cnblogs.com/asus119/archive/2011/10/11/2206841.html 版本一: 简单说来,可以这样理解: 一.解释 VCC:C=circ ...
- VCC、 VDD、VEE、VSS 电压理解
VCC. VDD.VEE.VSS 版本一: 简单说来,可以这样理解: 一.解释 VCC:C=circuit 表示电路的意思, 即接入电路的电压: VDD:D=device 表示器件的意思, 即器件内部 ...
- 无连接运输的UDP、可靠数据传输原理、面向连接运输的TCP
由[RFC 768]定义的UDP只是做了运输协议能够做的最少工作.除了复用/分解功能极少量的差错检测外,它几乎没有对IP增加别的东西.如果应用程序开发人员选择UDP而不是TCP,则该应用程序差不多就是 ...
- [IC]Lithograph(1)光刻技术分析与展望
文章主体转载自: 1.zol摩尔定律全靠它 CPU光刻技术分析与展望 2.wiki:Extreme ultraviolet lithography 3.ITRS 2012 1. 光刻技术组成和关键点 ...
随机推荐
- 曾经做的一个JS小游戏——《Battle City》
今天改网盘密码时,找到了个很久前的东西:JavaScript版的坦克大战.07年的夏天制作花了好多个夜晚制作,那段着迷JS游戏的疯狂时光.但因为后来众多浏览器的出现,导致了游戏兼容性大大的下降,最终放 ...
- java 子类继承父类 -- 重写、覆盖
class Foo { public int a; public static final String str = "foo"; public Foo() { a = 3; } ...
- 使用自定义 JsonConverter 解决 long 类型在前端的精度问题
问题 Javascript 的 number 类型存在精度限制,浏览器反序列化 JSON 时,无法完整保留 long 类型的精度. 在 JSON 序列化时将 long 转换为 string 进行传递就 ...
- HTML5 表单新的 Input 类型
H5新增了电子邮箱,手机号码,网址,数量,搜索,范围,颜色选择,时间日期等input类型 1.电子邮箱 type="email" 提供电子邮箱格式验证 如果格式不对,会阻止表单提交 ...
- .Net知识技能大全
.Net知识技能大全 更多请见https://www.dotnetshare.com C#常见运算符 一元运算符(+.-.!.~.++.--) 算术运算符(*./.%.+. – ) 移位运算符(< ...
- JavaScript是按顺序执行的吗?聊聊JavaScript中的变量提升
作为一位前端开发者,我们经常会听到这么一句话:"JavaScript的执行是按照顺序自上而下依次执行的."这句话说的并没有错.但是它似乎又好像不完全对.我们先来看以下这段代码.你觉 ...
- vscode 你想要的配置
配置用户代码片段 文件 → 首选项 → 配置用户代码片段 比如配置一个vue3的代码片段: { "vue3-code": { "prefix": "v ...
- 关于 Span 的一切:探索新的 .NET 明星: 4. Span<T> 和 Memory<T> 是如何与 .NET 库集成的?
4. Span<T> 和 Memory<T> 是如何与 .NET 库集成的? 1. Span<T> 是什么? 2. Span<T> 是如何实现的? 3. ...
- Tomcat 已集成 CROS Fitler ExpiresFilter 等一堆常用 Filter
http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html 再也不需要三方包提供的 filter 了
- UML之包与包图
了解UML的人都知道UML中也有包的概念,包在UML中作用与面向对象编程语言中类似,它是管理对象的工具,也是解决对象同名冲突的手段. 在UML中,包的表示图形是一个左上角带标签的矩形,而包名可以标注于 ...