Codeforces 题面传送门 & 洛谷题面传送门

大家都是暴力找生成树然后跳路径,代码不到 50 行(暴论)的一说……好,那本蒟蒻决定提供一种代码 150 行,但复杂度也是线性的分类讨论做法。

首先大家都是从“如果存在两个环相交,就一定存在符合要求的路径”这个性质入手的,而我不是。注意到题目条件涉及“简单路径”,因此我首先想到的是,如果两个点 \(u,v\) 之间存在三条互不相交的路径,那么 \(u,v\) 在同一个点双连通分量中必定是必要条件,因此不同点双之间的点必然是没有贡献的,我们只用考虑同一点双中的点即可。

考虑什么样的点双中存在符合条件的两个点,通过手玩数据我发现,一个环的情况显然是不行的,其余情况都存在符合要求的两个点。因此直觉告诉我,一个点双中存在符合条件的两个点的充要条件就是这个点双不是一个环。事实也的确如此,考虑怎么构造符合条件的路径。

我们考虑随便定一个根节点,并以从根节点开始 DFS 找出点双的一棵 DFS 树,那么由于原图是一个点双,必然有与根节点 \(r\) 相连的树边只有一条,因为根据 DFS 树的性质,该点双中所有非树边都是连接 DFS 树上某个点与其祖先的边,因此如果根节点存在多个分叉,那么这些分叉代表的子树之间两两是没有边的,换句话说,去掉根节点后图不连通,不符合点双的定义。考虑就此分三种情况讨论:

  1. 存在两个及以上与根节点相连的非树边

    假设这两条非树边分别是 \(r\to u\) 和 \(r\to v\),那么考虑找出 \(w=\text{LCA}(u,v)\),那么 \(r,w\) 之间存在三条符合要求的路径,一条是 \(w\) 直接跳到 \(r\),一条是 \(w\) 向下走到 \(u\),\(u\) 再到 \(r\),一条是 \(w\) 向下走到 \(v\),\(v\) 再到 \(r\)​​。

    如下图所示,三种颜色分别代表了三条不同的路径:

  2. 只有一条与根节点相连的非树边

    显然,由于原图是一个点双,不能不存在非树边与根节点相连,否则去掉根节点唯一的儿子后,图就不连通了(这里假定点双大小 \(\ge 3\),如果点双大小 \(\le 2\) 直接判掉即可)

    那么我们不妨假设这条非树边为 \(r\to u\),到这里我们继续分类讨论:

    1. 如果 \(u\) 不是叶子

      那么我们考虑找到 \(u\) 子树中一个叶子 \(v\),再找到所有与 \(v\) 相连的点中,深度最浅的那个点 \(w\),还是根据图是一个点双这个性质,必然有 \(w\) 的深度严格浅于 \(u\) 的深度,否则去掉 \(w\) 之后图不连通,这样考虑 \(u\to w\) 的路径,有以下三条:

      • \(u\) 向上直接跳到 \(w\)
      • \(u\) 跳到根节点 \(r\),再向下走到 \(w\)
      • \(u\) 向下走到 \(v\),再向上跳到 \(w\)​

    2. 如果 \(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(点双连通分量+分类讨论)的更多相关文章

  1. D8 双连通分量

    记得有个梗那一天,zw学生zzh大佬说逃不掉的路变成a不掉的题哈哈哈哈: 分离的路径: BZOJ 1718POJ 3177LUOGU 286: 思路:在同一个边双连通分量中,任意两点都有至少两条独立路 ...

  2. [Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分)

    [Codeforces 555E]Case of Computer Network(Tarjan求边-双连通分量+树上差分) 题面 给出一个无向图,以及q条有向路径.问是否存在一种给边定向的方案,使得 ...

  3. codeforces 962F.simple cycle(tarjan/点双连通分量)

    题目连接:http://codeforces.com/contest/962/problem/F 题目大意是定义一个simple cycle为从一个节点开始绕环走一遍能经过simple cycle内任 ...

  4. CodeForces 97 E. Leaders(点双连通分量 + 倍增)

    题意 给你一个有 \(n\) 个点 \(m\) 条边的无向图,有 \(q\) 次询问,每次询问两个点 \(u, v\) 之间是否存在长度为奇数的简单路径. \(1 \le n, m, q \le 10 ...

  5. Simple Cycles Edges CodeForces - 962F(点双连通分量)

    题意: 求出简单环的所有边,简单环即为边在一个环内 解析: 求出点双连通分量,如果一个连通分量的点数和边数相等,则为一个简单环 点双连通分量  任意两个点都至少存在两条点不重复的路径  即任意两条边都 ...

  6. Gym - 100676H H. Capital City (边双连通分量缩点+树的直径)

    https://vjudge.net/problem/Gym-100676H 题意: 给出一个n个城市,城市之间有距离为w的边,现在要选一个中心城市,使得该城市到其余城市的最大距离最短.如果有一些城市 ...

  7. 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分

    E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...

  8. 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 ...

  9. POJ3352 Road Construction (双连通分量)

    Road Construction Time Limit:2000MS    Memory Limit:65536KB    64bit IO Format:%I64d & %I64u Sub ...

随机推荐

  1. C#与java TCP通道加密通信

    背景说明 公司收费系统需要与银行做实时代收对接,业务协议使用我们收费系统的标准.但是银行要求在业务协议的基础上,使用银行的加密规则. 采用MD5计算报文摘要,保证数据的完整性 采用RSA256对摘要进 ...

  2. 想要彻底搞懂大厂是如何实现Redis高可用的?看这篇文章就够了!(1.2W字,建议收藏)

    高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间. 假设系统一直能够提供服务,我们说系统的可用性是100%.如果 ...

  3. 【c++ Prime 学习笔记】第13章 拷贝控制

    定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...

  4. Ruby on Rails 单元测试

    Ruby on Rails 单元测试 为什么要写测试文件? 软件开发中,一个重要的环节就是编写测试文件,对代码进行单元测试,确保程序各部分功能执行正确.但是,这一环节很容易被我们轻视,认为进行单元测试 ...

  5. 基于自定义Validator来验证枚举类型

    基于自定义Validator来验证枚举类型 一.背景 二.技术要点 三.实现一个自定义枚举校验. 1.需求. 2.实现步骤 1.自定义一个 Sex 枚举. 2.自定义一个 Enum 注解 3.编写具体 ...

  6. NOIP 模拟 $79\; \rm y$

    题解 \(by\;zj\varphi\) NOIP2013 的原题 最简单的思路就是一个 bfs,可以拿到 \(70pts\) 75pts #include<bits/stdc++.h> ...

  7. 一文读懂Android进程及TCP动态心跳保活

    一直以来,APP进程保活都是 各软件提供商 和 个人开发者 头疼的问题.毕竟一切的商业模式都建立在用户对APP的使用上,因此保证APP进程的唤醒,提升用户的使用时间,便是软件提供商和个人开发者的永恒追 ...

  8. USB线上/串口/I2C引脚串联电阻的作用

    对引脚的保护. 第一是阻抗匹配.因为信号源的阻抗很低,跟信号线之间阻抗不匹配,串上一个电阻后,可改善匹配情况,以减少反射,避免振荡等. 第二是可以减少信号边沿的陡峭程度,从而减少高频噪声以及过冲等.因 ...

  9. linux shell 基本语法之快速上手shell编程

    从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁.用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操 ...

  10. JVM:垃圾收集器与对象的"存活"问题

    垃圾收集器垃圾收集(Garbage Collection,GC).当需要排查各种内存溢出.内存泄露问题时,当垃圾收集成为系统更高并发量的瓶颈时,我们需要去了解GC和内存分配. 检查对象的"存 ...