Codeforces 521E - Cycling City(点双连通分量+分类讨论)
大家都是暴力找生成树然后跳路径,代码不到 50 行(暴论)的一说……好,那本蒟蒻决定提供一种代码 150 行,但复杂度也是线性的分类讨论做法。
首先大家都是从“如果存在两个环相交,就一定存在符合要求的路径”这个性质入手的,而我不是。注意到题目条件涉及“简单路径”,因此我首先想到的是,如果两个点 \(u,v\) 之间存在三条互不相交的路径,那么 \(u,v\) 在同一个点双连通分量中必定是必要条件,因此不同点双之间的点必然是没有贡献的,我们只用考虑同一点双中的点即可。
考虑什么样的点双中存在符合条件的两个点,通过手玩数据我发现,一个环的情况显然是不行的,其余情况都存在符合要求的两个点。因此直觉告诉我,一个点双中存在符合条件的两个点的充要条件就是这个点双不是一个环。事实也的确如此,考虑怎么构造符合条件的路径。
我们考虑随便定一个根节点,并以从根节点开始 DFS 找出点双的一棵 DFS 树,那么由于原图是一个点双,必然有与根节点 \(r\) 相连的树边只有一条,因为根据 DFS 树的性质,该点双中所有非树边都是连接 DFS 树上某个点与其祖先的边,因此如果根节点存在多个分叉,那么这些分叉代表的子树之间两两是没有边的,换句话说,去掉根节点后图不连通,不符合点双的定义。考虑就此分三种情况讨论:
存在两个及以上与根节点相连的非树边
假设这两条非树边分别是 \(r\to u\) 和 \(r\to v\),那么考虑找出 \(w=\text{LCA}(u,v)\),那么 \(r,w\) 之间存在三条符合要求的路径,一条是 \(w\) 直接跳到 \(r\),一条是 \(w\) 向下走到 \(u\),\(u\) 再到 \(r\),一条是 \(w\) 向下走到 \(v\),\(v\) 再到 \(r\)。
如下图所示,三种颜色分别代表了三条不同的路径:

只有一条与根节点相连的非树边
显然,由于原图是一个点双,不能不存在非树边与根节点相连,否则去掉根节点唯一的儿子后,图就不连通了(这里假定点双大小 \(\ge 3\),如果点双大小 \(\le 2\) 直接判掉即可)
那么我们不妨假设这条非树边为 \(r\to u\),到这里我们继续分类讨论:
如果 \(u\) 不是叶子
那么我们考虑找到 \(u\) 子树中一个叶子 \(v\),再找到所有与 \(v\) 相连的点中,深度最浅的那个点 \(w\),还是根据图是一个点双这个性质,必然有 \(w\) 的深度严格浅于 \(u\) 的深度,否则去掉 \(w\) 之后图不连通,这样考虑 \(u\to w\) 的路径,有以下三条:
- \(u\) 向上直接跳到 \(w\)
- \(u\) 跳到根节点 \(r\),再向下走到 \(w\)
- \(u\) 向下走到 \(v\),再向上跳到 \(w\)

如果 \(u\) 是叶子
我们考虑找到一条非树边,不同于 \(r\to u\) 这条边,并且这两条边至少有一个端点在 \(u\to r\) 这条链上,可以说明我们总能找到这样的边,否则图不满足点双的性质,读者自证不难,那么我们假设这条边为 \(x,y\),其中 \(x\) 为深度较小者,设离 \(y\) 最近的、在 \(u\to r\) 这条链上的节点为 \(w\),那么考虑构造这样三条 \(w\to x\) 的路径:
- \(w\) 直接向上跳到 \(x\)
- \(w\) 向下走到 \(y\),再走到 \(x\)
- \(w\) 向下走到 \(u\),跳到 \(r\),再向下走到 \(x\)

分类讨论一下即可,复杂度 \(\mathcal O(n)\)
const int MAXN=2e5;
int n,m,U[MAXN+5],V[MAXN+5];
struct graph{
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
} g,ng,t;
int dfn[MAXN+5],low[MAXN+5],tim=0;
int stk[MAXN+5],tp=0,bel[MAXN+5],cmp=0,in_stk[MAXN+5];
void tarjan(int x){
// printf("tarjan %d\n",x);
dfn[x]=low[x]=++tim;
for(int e=g.hd[x];e;e=g.nxt[e]){
int y=g.to[e];
if(!dfn[y]){
stk[++tp]=e>>1;in_stk[e>>1]=1;
tarjan(y);chkmin(low[x],low[y]);
if(low[y]>=dfn[x]){
// printf("find an edcc %d %d\n",x,y);
cmp++;int o;do{
o=stk[tp--];bel[o]=cmp;
in_stk[o]=0;
} while(o^(e>>1));
}
} else {
chkmin(low[x],dfn[y]);
if(dfn[y]<dfn[x]&&!in_stk[e>>1]){
stk[++tp]=e>>1;in_stk[e>>1]=1;
}
}
}
}
vector<int> bp[MAXN+5];
int in[MAXN+5],fa[MAXN+5],dep[MAXN+5],deg[MAXN+5];
bool vis[MAXN+5],ont[MAXN+5];
void dfs(int x){
vis[x]=1;
for(int e=ng.hd[x];e;e=ng.nxt[e]){
int y=ng.to[e];
if(!vis[y]){
ont[e>>1]=1;t.adde(x,y);
fa[y]=x;deg[x]++;deg[y]++;dep[y]=dep[x]+1;dfs(y);
}
}
}
void prt(vector<int> res1,vector<int> res2,vector<int> res3){
puts("YES");
printf("%d",res1.size());for(int x:res1) printf(" %d",x);printf("\n");
printf("%d",res2.size());for(int x:res2) printf(" %d",x);printf("\n");
printf("%d",res3.size());for(int x:res3) printf(" %d",x);printf("\n");
exit(0);
}
void work(int id){
for(int e:bp[id]) ng.adde(U[e],V[e]),ng.adde(V[e],U[e]);
dfs(U[bp[id][0]]);int rt=U[bp[id][0]],cnt=0;
for(int e=ng.hd[rt];e;e=ng.nxt[e]) cnt+=(!ont[e>>1]);
assert(cnt>=1);
if(cnt>=2){
int x=0,y=0;vector<int> p1,p2,p3;p1.pb(rt),p2.pb(rt);
for(int e=ng.hd[rt];e;e=ng.nxt[e]) if(!ont[e>>1]){
if(!x) x=ng.to[e];else if(!y) y=ng.to[e];
} if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) p1.pb(x),x=fa[x];
while(x^y) p1.pb(x),p2.pb(y),x=fa[x],y=fa[y];
p1.pb(x);p2.pb(x);
while(x^rt) p3.pb(x),x=fa[x];p3.pb(rt);
reverse(p3.begin(),p3.end());prt(p1,p2,p3);
} else {
int p=0,pe=0;
for(int e=ng.hd[rt];e;e=ng.nxt[e]) if(!ont[e>>1])
p=ng.to[e],pe=e>>1;
if(deg[p]!=1){
int q=p;while(deg[q]!=1){
for(int e=t.hd[q];e;e=t.nxt[e])
if(t.to[e]!=fa[q]){q=t.to[e];break;}
} int r=0;for(int e=ng.hd[q];e;e=ng.nxt[e])
if(!r||dep[r]>dep[ng.to[e]]) r=ng.to[e];
vector<int> p1,p2,p3;int cp=p,cq=q;
while(cp^r) p1.pb(cp),cp=fa[cp];p1.pb(cp);
while(cp^rt) p2.pb(cp),cp=fa[cp];p2.pb(rt);
reverse(p2.begin(),p2.end());p2.insert(p2.begin(),p);
while(cq^p) p3.pb(cq),cq=fa[cq];p3.pb(cq);
reverse(p3.begin(),p3.end());p3.pb(r);
prt(p1,p2,p3);
} else {
int cp=p;static bool on_ch[MAXN+5]={0};
while(cp^rt) on_ch[cp]=1,cp=fa[cp];on_ch[rt]=1;
int u=0,v=0;vector<int> p1,p2,p3;
for(int e=1;e<=(ng.ec>>1);e++) if(!ont[e]&&e!=pe){
int x=ng.to[e<<1],y=ng.to[e<<1|1];
if(on_ch[x]||on_ch[y]){u=x;v=y;break;}
} if(dep[u]<dep[v]) swap(u,v);
while(!on_ch[u]) p1.pb(u),u=fa[u];p1.pb(u);
reverse(p1.begin(),p1.end());p1.pb(v);
int cu=u,cv=v;while(cu^v) p2.pb(cu),cu=fa[cu];p2.pb(v);
cu=p;while(cv^rt) p3.pb(cv),cv=fa[cv];p3.pb(rt);
while(cu^u) p3.pb(cu),cu=fa[cu];p3.pb(cu);
reverse(p3.begin(),p3.end());prt(p1,p2,p3);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&U[i],&V[i]);
g.adde(U[i],V[i]);g.adde(V[i],U[i]);
} for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=m;i++) bp[bel[i]].pb(i);
// for(int i=1;i<=m;i++) printf("%d\n",bel[i]);
for(int id=1;id<=cmp;id++){
int cc=0;
for(int e:bp[id]){
if(!in[U[e]]) in[U[e]]=1,cc++;
if(!in[V[e]]) in[V[e]]=1,cc++;
} if(cc<bp[id].size()){
work(id);
} for(int e:bp[id]) in[U[e]]=in[V[e]]=0;
} puts("NO");
return 0;
}
Codeforces 521E - Cycling City(点双连通分量+分类讨论)的更多相关文章
- D8 双连通分量
记得有个梗那一天,zw学生zzh大佬说逃不掉的路变成a不掉的题哈哈哈哈: 分离的路径: BZOJ 1718POJ 3177LUOGU 286: 思路:在同一个边双连通分量中,任意两点都有至少两条独立路 ...
- [Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分)
[Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分) 题面 给出一个无向图,以及q条有向路径.问是否存在一种给边定向的方案,使得 ...
- codeforces 962F.simple cycle(tarjan/点双连通分量)
题目连接:http://codeforces.com/contest/962/problem/F 题目大意是定义一个simple cycle为从一个节点开始绕环走一遍能经过simple cycle内任 ...
- CodeForces 97 E. Leaders(点双连通分量 + 倍增)
题意 给你一个有 \(n\) 个点 \(m\) 条边的无向图,有 \(q\) 次询问,每次询问两个点 \(u, v\) 之间是否存在长度为奇数的简单路径. \(1 \le n, m, q \le 10 ...
- Simple Cycles Edges CodeForces - 962F(点双连通分量)
题意: 求出简单环的所有边,简单环即为边在一个环内 解析: 求出点双连通分量,如果一个连通分量的点数和边数相等,则为一个简单环 点双连通分量 任意两个点都至少存在两条点不重复的路径 即任意两条边都 ...
- Gym - 100676H H. Capital City (边双连通分量缩点+树的直径)
https://vjudge.net/problem/Gym-100676H 题意: 给出一个n个城市,城市之间有距离为w的边,现在要选一个中心城市,使得该城市到其余城市的最大距离最短.如果有一些城市 ...
- 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分
E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...
- HDU 3686 Traffic Real Time Query System(双连通分量缩点+LCA)(2010 Asia Hangzhou Regional Contest)
Problem Description City C is really a nightmare of all drivers for its traffic jams. To solve the t ...
- POJ3352 Road Construction (双连通分量)
Road Construction Time Limit:2000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u Sub ...
随机推荐
- Golang通脉之包的管理
在工程化的开发项目中,Go语言的源码复用是建立在包(package)基础之上的. 包(package)是多个Go源码的集合,是一种高级的代码复用方案,Go语言提供了很多内置包,如fmt.os.io等. ...
- vue3.x新特性之setup函数,看完就会用了
最近有小伙伴跟我聊起setup函数,因为习惯了vue2.x的写法导致了,setup用起来觉得奇奇怪怪的,在一些api混编的情况下,代码变得更加混乱了,个人觉得在工程化思想比较强的团队中使用setup确 ...
- MySQL:提高笔记-4
MySQL:提高笔记-4 学完基础的语法后,进一步对 MySQL 进行学习,前几篇为: MySQL:提高笔记-1 MySQL:提高笔记-2 MySQL:提高笔记-3 MySQL:提高笔记-4,本文 说 ...
- VS2017+QT5.12.10+QGIS3.16环境搭建及开发全流程
题记:大力发展生产力,助力高效采集.(转载请注明出处https://www.cnblogs.com/1024bytes/p/15477374.html) 本篇随笔分为五个部分: 一.获取QGIS3.1 ...
- C/C++编程笔记:浪漫流星雨表白装b程序
作为一个未来可能会成为一个专业程序员的小伙们,不知道你们现在学到哪里了,学了点东西之后有没有想在你女朋友面前装个大大的b呢,今天小编就给你一个机会来研究一下下边的代码吧,保证大写的N,当然大佬是排除在 ...
- Java之父 James Gosling 发表博文 《Too Soon》纪念乔布斯。
几个礼拜前,我们还在讨论乔布斯的辞职.虽然我们都知道这意味着什么,但是我没有想到一切来的如此之快.已经有很多关于这件事情的文章了,特别是"经济学人"的这篇文章. 乔布斯是一个很独特 ...
- python解释器的下载与安装
python解释器 1. 什么是python解释器 用一种能让电脑听的懂得语言,使得电脑可以听从人们的指令去进行工作(翻译官) Python解释器本身也是个程序, 它是解释执行Python代码的,所以 ...
- mysql 的安装方式
一.rpm包安装方式 mysql-community-client-5.7.18-1.el7.x86_64.rpm 客户端 mysql-community-devel-5.7.18-1.el7.x86 ...
- k8s入坑之路(5)kube-apiserver详解
API Server kube-apiserver 是 Kubernetes 最重要的核心组件之一,主要提供以下的功能 提供集群管理的 REST API 接口,包括认证授权.数据校验以及集群状态变更等 ...
- Webshell 一句话木马
Webshell介绍 什么是 WebShell webshell就是以asp.php.jsp或者cgj等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门 由于 webshell其大多是 ...